blob: 8ce5cb0b01cbea290aa5f192b13dfa22b9b64042 [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.errors.Unreachable;
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.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.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
public class AnnotationRemover {
private final AppInfoWithLiveness appInfo;
private final GraphLense lense;
private final ProguardKeepAttributes keep;
private final InternalOptions options;
public AnnotationRemover(AppInfoWithLiveness appInfo, GraphLense lense, InternalOptions options) {
this.appInfo = appInfo;
this.lense = lense;
this.keep = options.getProguardConfiguration().getKeepAttributes();
this.options = options;
}
/**
* Used to filter annotations on classes, methods and fields.
*/
private boolean filterAnnotations(DexAnnotation annotation) {
return shouldKeepAnnotation(
annotation, isAnnotationTypeLive(annotation), appInfo.dexItemFactory, 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 (options.canUseParameterNameAnnotations()
&& 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(appInfo.dexItemFactory);
DexClass definition = appInfo.definitionFor(annotationType);
// TODO(b/73102187): How to handle annotations without definition.
if (options.enableTreeShaking && definition == null) {
return false;
}
return definition == null || definition.isLibraryClass()
|| appInfo.liveTypes.contains(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(ProguardConfiguration.Builder compatibility) {
keep.ensureValid(options.forceProguardCompatibility, compatibility);
return this;
}
public void run() {
for (DexProgramClass clazz : 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) {
DexType annotationType = original.type.toBaseType(appInfo.dexItemFactory);
return original.rewrite(
lense::lookupType,
element -> rewriteAnnotationElement(lense.lookupType(annotationType), element));
}
private DexAnnotationElement rewriteAnnotationElement(
DexType annotationType, DexAnnotationElement original) {
DexClass definition = appInfo.definitionFor(annotationType);
// TODO(b/73102187): How to handle annotations without definition.
if (definition == null) {
return original;
}
assert definition.isInterface();
boolean liveGetter = Arrays.stream(definition.virtualMethods())
.anyMatch(method -> method.method.name == original.name);
return liveGetter ? original : null;
}
private boolean enclosingMethodPinned(DexClass clazz) {
return clazz.getEnclosingMethod() != null
&& clazz.getEnclosingMethod().getEnclosingClass() != null
&& appInfo.isPinned(clazz.getEnclosingMethod().getEnclosingClass());
}
private boolean innerClassPinned(DexClass clazz) {
List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
for (InnerClassAttribute innerClass : innerClasses) {
if (appInfo.isPinned(innerClass.getInner())) {
return true;
}
if (appInfo.isPinned(innerClass.getOuter())) {
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.
if (appInfo.isPinned(clazz.type)
|| enclosingMethodPinned(clazz)
|| innerClassPinned(clazz)
|| options.forceProguardCompatibility) {
if (!keep.enclosingMethod) {
clazz.clearEnclosingMethod();
}
if (!keep.innerClasses) {
clazz.clearInnerClasses();
}
} 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();
}
}
}