blob: 693058ba8fd3bf6f396d58212da5c130d576e2bf [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.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
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.GraphLense;
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.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
public class AnnotationRemover {
private final AppView<AppInfoWithLiveness> appView;
private final ProguardKeepAttributes keep;
private final Set<DexType> classesToRetainInnerClassAttributeFor;
public AnnotationRemover(
AppView<AppInfoWithLiveness> appView, Set<DexType> classesToRetainInnerClassAttributeFor) {
this.appView = appView;
this.keep = appView.options().getProguardConfiguration().getKeepAttributes();
this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
}
/**
* Used to filter annotations on classes, methods and fields.
*/
private boolean filterAnnotations(DexAnnotation annotation) {
return shouldKeepAnnotation(
annotation, isAnnotationTypeLive(annotation), appView.dexItemFactory(), appView.options());
}
static boolean shouldKeepAnnotation(
DexAnnotation annotation,
boolean isAnnotationTypeLive,
DexItemFactory dexItemFactory,
InternalOptions options) {
ProguardKeepAttributes config =
options.getProguardConfiguration() != null
? options.getProguardConfiguration().getKeepAttributes()
: ProguardKeepAttributes.fromPatterns(ImmutableList.of());
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);
if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
return true;
}
if (config.signature && DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory)) {
return true;
}
if (config.sourceDebugExtension
&& DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
return true;
}
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) {
DexType annotationType = annotation.annotation.type.toBaseType(appView.dexItemFactory());
DexClass definition = appView.definitionFor(annotationType);
// TODO(b/73102187): How to handle annotations without definition.
if (appView.options().isShrinking() && definition == null) {
return false;
}
return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
}
/**
* Used to filter annotations on parameters.
*/
private boolean filterParameterAnnotations(DexAnnotation annotation) {
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;
}
}
private static boolean hasSignatureAnnotation(DexProgramClass clazz, DexItemFactory itemFactory) {
for (DexAnnotation annotation : clazz.annotations.annotations) {
if (DexAnnotation.isSignatureAnnotation(annotation, itemFactory)) {
return true;
}
}
return false;
}
public static Set<DexType> computeClassesToRetainInnerClassAttributeFor(
AppView<? extends AppInfoWithLiveness> appView) {
// 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.
if (appView.options().forceProguardCompatibility
|| !appView.options().getProguardConfiguration().getKeepAttributes().innerClasses) {
return Collections.emptySet();
}
// 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();
Iterable<DexProgramClass> programClasses = appView.appInfo().classes();
for (DexProgramClass clazz : programClasses) {
if (hasSignatureAnnotation(clazz, appView.dexItemFactory())) {
genericClasses.add(clazz);
}
for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
if ((innerClassAttribute.getAccess() & Constants.ACC_STATIC) == 0
&& innerClassAttribute.getOuter() == clazz.type) {
enclosingClasses.put(innerClassAttribute.getInner(), clazz);
}
}
}
Set<DexType> result = Sets.newIdentityHashSet();
for (DexProgramClass clazz : programClasses) {
// 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.type)) {
for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
DexType inner = innerClassAttribute.getInner();
if (appView.appInfo().isNonProgramTypeOrLiveProgramType(inner)) {
result.add(inner);
}
DexType context = innerClassAttribute.getLiveContext(appView.appInfo());
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);
}
}
return result;
}
public void run() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
stripAttributes(clazz);
clazz.annotations = clazz.annotations.rewrite(this::rewriteAnnotation);
clazz.forEachMethod(this::processMethod);
clazz.forEachField(this::processField);
}
}
private void processMethod(DexEncodedMethod method) {
method.annotations = method.annotations.rewrite(this::rewriteAnnotation);
method.parameterAnnotationsList =
method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
}
private void processField(DexEncodedField field) {
field.annotations = field.annotations.rewrite(this::rewriteAnnotation);
}
private DexAnnotation rewriteAnnotation(DexAnnotation original) {
// Check if we should keep this annotation first.
if (!filterAnnotations(original)) {
return null;
}
// Then, filter out values that refer to dead definitions.
return original.rewrite(this::rewriteEncodedAnnotation);
}
private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
GraphLense graphLense = appView.graphLense();
DexType annotationType = original.type.toBaseType(appView.dexItemFactory());
DexType rewrittenType = graphLense.lookupType(annotationType);
DexEncodedAnnotation rewrite =
original.rewrite(
graphLense::lookupType, element -> rewriteAnnotationElement(rewrittenType, element));
assert rewrite != null;
DexClass annotationClass = appView.definitionFor(rewrittenType);
assert annotationClass == null
|| appView.appInfo().isNonProgramTypeOrLiveProgramType(rewrittenType);
return rewrite;
}
private DexAnnotationElement rewriteAnnotationElement(
DexType annotationType, DexAnnotationElement original) {
DexClass definition = appView.definitionFor(annotationType);
// TODO(b/73102187): How to handle annotations without definition.
if (definition == null) {
return original;
}
assert definition.isInterface();
boolean liveGetter =
definition.virtualMethods().stream()
.anyMatch(method -> method.method.name == original.name);
return liveGetter ? original : null;
}
private boolean enclosingMethodPinned(DexClass clazz) {
return clazz.getEnclosingMethod() != null
&& clazz.getEnclosingMethod().getEnclosingClass() != null
&& appView.appInfo().isPinned(clazz.getEnclosingMethod().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(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.clearEnclosingMethod();
}
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;
}
if (appView.appInfo().isPinned(ica.getOuter())) {
return false;
}
if (finalKeepForThisInnerClass && ica.getInner() == clazz.type) {
return false;
}
if (finalKeepForThisEnclosingClass
&& ica.getOuter() == 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.clearEnclosingMethod();
clazz.clearInnerClasses();
}
}
public static void clearAnnotations(AppView<?> appView) {
for (DexProgramClass clazz : appView.appInfo().classes()) {
clazz.annotations = DexAnnotationSet.empty();
for (DexEncodedMethod method : clazz.methods()) {
method.annotations = DexAnnotationSet.empty();
}
for (DexEncodedField field : clazz.fields()) {
field.annotations = DexAnnotationSet.empty();
}
}
}
}