| // 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.AppView; |
| import com.android.tools.r8.graph.DexAnnotation; |
| import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind; |
| 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.DexItemFactory; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.DexValue; |
| import com.android.tools.r8.graph.EnclosingMethodAttribute; |
| import com.android.tools.r8.graph.InnerClassAttribute; |
| import com.android.tools.r8.graph.ProgramDefinition; |
| import com.android.tools.r8.graph.ProgramMember; |
| import com.android.tools.r8.graph.lens.GraphLens; |
| import com.android.tools.r8.kotlin.KotlinMemberLevelInfo; |
| import com.android.tools.r8.kotlin.KotlinPropertyInfo; |
| import com.android.tools.r8.shaking.Enqueuer.Mode; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Sets; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| |
| public class AnnotationRemover { |
| |
| private final AppView<AppInfoWithLiveness> appView; |
| private final Mode mode; |
| private final InternalOptions options; |
| private final Set<DexAnnotation> annotationsToRetain; |
| private final ProguardKeepAttributes keep; |
| private final Set<DexType> removedClasses; |
| |
| private AnnotationRemover( |
| AppView<AppInfoWithLiveness> appView, |
| Set<DexAnnotation> annotationsToRetain, |
| Mode mode, |
| Set<DexType> removedClasses) { |
| this.appView = appView; |
| this.mode = mode; |
| this.options = appView.options(); |
| this.annotationsToRetain = annotationsToRetain; |
| this.keep = appView.options().getProguardConfiguration().getKeepAttributes(); |
| this.removedClasses = removedClasses; |
| } |
| |
| public static Builder builder(Mode mode) { |
| return new Builder(mode); |
| } |
| |
| /** Used to filter annotations on classes, methods and fields. */ |
| private boolean filterAnnotations( |
| ProgramDefinition holder, |
| DexAnnotation annotation, |
| AnnotatedKind kind, |
| KeepInfo<?, ?> keepInfo) { |
| return annotationsToRetain.contains(annotation) |
| || shouldKeepAnnotation( |
| appView, holder, annotation, isAnnotationTypeLive(annotation), kind, mode, keepInfo); |
| } |
| |
| public static boolean shouldKeepAnnotation( |
| AppView<?> appView, |
| ProgramDefinition holder, |
| DexAnnotation annotation, |
| boolean isAnnotationTypeLive, |
| AnnotatedKind kind, |
| Mode mode, |
| KeepInfo<?, ?> keepInfo) { |
| // If we cannot run the AnnotationRemover we are keeping the annotation. |
| InternalOptions options = appView.options(); |
| if (!options.isShrinking()) { |
| return true; |
| } |
| |
| boolean isAnnotationOnAnnotationClass = |
| holder.isProgramClass() && holder.asProgramClass().isAnnotation(); |
| |
| ProguardKeepAttributes config = |
| options.getProguardConfiguration() != null |
| ? options.getProguardConfiguration().getKeepAttributes() |
| : ProguardKeepAttributes.fromPatterns(ImmutableList.of()); |
| |
| DexItemFactory dexItemFactory = appView.dexItemFactory(); |
| switch (annotation.visibility) { |
| case DexAnnotation.VISIBILITY_SYSTEM: |
| if (kind.isParameter()) { |
| return false; |
| } |
| // 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 options.passthroughDexCode |
| || !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory); |
| if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) { |
| return true; |
| } |
| if (DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) { |
| assert holder.isProgramClass(); |
| appView.setSourceDebugExtensionForType( |
| holder.asProgramClass(), annotation.annotation.elements[0].value.asDexValueString()); |
| return config.sourceDebugExtension; |
| } |
| if (config.methodParameters |
| && DexAnnotation.isParameterNameAnnotation(annotation, dexItemFactory)) { |
| return true; |
| } |
| if (isAnnotationOnAnnotationClass |
| && DexAnnotation.isAnnotationDefaultAnnotation(annotation, dexItemFactory) |
| && shouldRetainAnnotationDefaultAnnotationOnAnnotationClass(annotation)) { |
| return true; |
| } |
| return false; |
| |
| case DexAnnotation.VISIBILITY_RUNTIME: |
| if (isAnnotationOnAnnotationClass |
| && DexAnnotation.isJavaLangRetentionAnnotation(annotation, dexItemFactory) |
| && shouldRetainRetentionAnnotationOnAnnotationClass(annotation, dexItemFactory)) { |
| return true; |
| } |
| |
| if (kind.isParameter()) { |
| KeepMethodInfo methodInfo = keepInfo.asMethodInfo(); |
| if (methodInfo == null || !options.isKeepRuntimeVisibleParameterAnnotationsEnabled()) { |
| return false; |
| } |
| return !methodInfo.isParameterAnnotationRemovalAllowed(options) && isAnnotationTypeLive; |
| } |
| |
| if (annotation.isTypeAnnotation()) { |
| if (!options.isKeepRuntimeVisibleTypeAnnotationsEnabled()) { |
| return false; |
| } |
| return !keepInfo.isTypeAnnotationRemovalAllowed(options) && isAnnotationTypeLive; |
| } |
| |
| if (!options.isKeepRuntimeVisibleAnnotationsEnabled()) { |
| return false; |
| } |
| return !keepInfo.isAnnotationRemovalAllowed(options) && isAnnotationTypeLive; |
| |
| case DexAnnotation.VISIBILITY_BUILD: |
| if (annotation |
| .getAnnotationType() |
| .getDescriptor() |
| .startsWith(options.itemFactory.dalvikAnnotationOptimizationPrefix)) { |
| return true; |
| } |
| if (isComposableAnnotationToRetain(appView, annotation, kind, mode, options)) { |
| return true; |
| } |
| if (kind.isParameter()) { |
| KeepMethodInfo methodInfo = keepInfo.asMethodInfo(); |
| if (methodInfo == null || !options.isKeepRuntimeInvisibleParameterAnnotationsEnabled()) { |
| return false; |
| } |
| return !methodInfo.isParameterAnnotationRemovalAllowed(options) && isAnnotationTypeLive; |
| } |
| |
| if (annotation.isTypeAnnotation()) { |
| if (!options.isKeepRuntimeInvisibleTypeAnnotationsEnabled()) { |
| return false; |
| } |
| return !keepInfo.isTypeAnnotationRemovalAllowed(options) && isAnnotationTypeLive; |
| } |
| |
| if (!options.isKeepRuntimeInvisibleAnnotationsEnabled()) { |
| return false; |
| } |
| return !keepInfo.isAnnotationRemovalAllowed(options) && 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); |
| } |
| |
| public AnnotationRemover ensureValid() { |
| keep.ensureValid(appView.options().forceProguardCompatibility); |
| return this; |
| } |
| |
| public void run(ExecutorService executorService) throws ExecutionException { |
| ThreadUtils.processItems( |
| appView.appInfo().classes(), |
| this::run, |
| appView.options().getThreadingModule(), |
| executorService); |
| assert verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo(); |
| } |
| |
| private void run(DexProgramClass clazz) { |
| KeepClassInfo keepInfo = appView.getKeepInfo().getClassInfo(clazz); |
| removeAnnotations(clazz, keepInfo); |
| stripAttributes(clazz, keepInfo); |
| // Kotlin metadata for classes are removed in the KotlinMetadataEnqueuerExtension. Kotlin |
| // properties are split over fields and methods. Check if any is pinned before pruning the |
| // information. |
| Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet(); |
| clazz.forEachProgramMember(member -> processMember(member, clazz, pinnedKotlinProperties)); |
| clazz.forEachProgramMember( |
| member -> { |
| KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo(); |
| if (kotlinInfo.isProperty() |
| && !pinnedKotlinProperties.contains(kotlinInfo.asProperty().getReference())) { |
| member.clearKotlinInfo(); |
| } |
| }); |
| } |
| |
| private boolean verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo() { |
| for (DexProgramClass clazz : appView.appInfo().classes()) { |
| if (clazz.getKotlinInfo().isNoKotlinInformation()) { |
| clazz.forEachProgramMember( |
| member -> { |
| assert member.getKotlinInfo().isNoKotlinInformation() |
| : "Should have pruned kotlin info"; |
| }); |
| } |
| } |
| return true; |
| } |
| |
| private void processMember( |
| ProgramMember<?, ?> member, |
| DexProgramClass clazz, |
| Set<KotlinPropertyInfo> pinnedKotlinProperties) { |
| KeepMemberInfo<?, ?> memberInfo = appView.getKeepInfo().getMemberInfo(member); |
| removeAnnotations(member, memberInfo); |
| if (memberInfo.isSignatureRemovalAllowed(options)) { |
| member.clearGenericSignature(); |
| } |
| if (!member.getKotlinInfo().isProperty() |
| && memberInfo.isKotlinMetadataRemovalAllowed(clazz, options)) { |
| member.clearKotlinInfo(); |
| } |
| // Postpone removal of kotlin property info until we have seen all fields, setters and getters. |
| if (member.getKotlinInfo().isProperty() |
| && !memberInfo.isKotlinMetadataRemovalAllowed(clazz, options)) { |
| pinnedKotlinProperties.add(member.getKotlinInfo().asProperty().getReference()); |
| } |
| } |
| |
| private DexAnnotation rewriteAnnotation( |
| ProgramDefinition holder, |
| DexAnnotation original, |
| AnnotatedKind kind, |
| KeepInfo<?, ?> keepInfo) { |
| // Check if we should keep this annotation first. |
| if (filterAnnotations(holder, original, kind, keepInfo)) { |
| // 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; |
| } |
| |
| @SuppressWarnings("ReferenceEquality") |
| private DexAnnotationElement rewriteAnnotationElement( |
| DexType annotationType, DexAnnotationElement original) { |
| // The dalvik.annotation.AnnotationDefault is typically not on bootclasspath. However, if it |
| // is present, the definition does not define the 'value' getter but that is the spec: |
| // https://source.android.com/devices/tech/dalvik/dex-format#dalvik-annotation-default |
| // If the annotation matches the structural requirement keep it. |
| if (appView.dexItemFactory().annotationDefault.equals(annotationType) |
| && appView.dexItemFactory().valueString.equals(original.name)) { |
| return original; |
| } |
| // 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. |
| DexClass definition = appView.definitionFor(annotationType); |
| if (definition == null) { |
| return original; |
| } |
| assert definition.isInterface(); |
| boolean liveGetter = |
| definition |
| .getMethodCollection() |
| .hasVirtualMethods(method -> method.getReference().name == original.name); |
| return liveGetter ? original : null; |
| } |
| |
| private void removeAnnotations(ProgramDefinition definition, KeepInfo<?, ?> keepInfo) { |
| assert mode.isInitialTreeShaking() || annotationsToRetain.isEmpty(); |
| definition.rewriteAllAnnotations( |
| (annotation, kind) -> rewriteAnnotation(definition, annotation, kind, keepInfo)); |
| } |
| |
| private static boolean isComposableAnnotationToRetain( |
| AppView<?> appView, |
| DexAnnotation annotation, |
| AnnotatedKind kind, |
| Mode mode, |
| InternalOptions options) { |
| return options.getJetpackComposeOptions().isAnyOptimizationsEnabled() |
| && mode.isInitialTreeShaking() |
| && kind.isMethod() |
| && annotation |
| .getAnnotationType() |
| .isIdenticalTo(appView.getComposeReferences().composableType); |
| } |
| |
| private static boolean shouldRetainAnnotationDefaultAnnotationOnAnnotationClass( |
| DexAnnotation unusedAnnotation) { |
| // We currently always retain the @AnnotationDefault annotations for annotation classes. In full |
| // mode we could consider only retaining @AnnotationDefault annotations for pinned annotations, |
| // as this is consistent with removing all annotations for non-kept items. |
| return true; |
| } |
| |
| private static boolean shouldRetainRetentionAnnotationOnAnnotationClass( |
| DexAnnotation annotation, DexItemFactory dexItemFactory) { |
| // Retain @Retention annotations that are different from @Retention(RetentionPolicy.CLASS). |
| if (annotation.annotation.getNumberOfElements() != 1) { |
| return true; |
| } |
| DexAnnotationElement element = annotation.annotation.getElement(0); |
| if (element.name.isNotIdenticalTo(dexItemFactory.valueString)) { |
| return true; |
| } |
| DexValue value = element.getValue(); |
| if (!value.isDexValueEnum() |
| || value |
| .asDexValueEnum() |
| .getValue() |
| .isNotIdenticalTo(dexItemFactory.javaLangAnnotationRetentionPolicyMembers.CLASS)) { |
| return true; |
| } |
| return false; |
| } |
| |
| private void stripAttributes(DexProgramClass clazz, KeepClassInfo keepInfo) { |
| // 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. |
| // In full mode we remove the attribute if not both sides are kept. |
| clazz.removeEnclosingMethodAttribute( |
| enclosingMethodAttribute -> |
| keepInfo.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() && keepInfo.isSignatureRemovalAllowed(options)) { |
| clazz.clearClassSignature(); |
| } |
| if (keepInfo.isPermittedSubclassesRemovalAllowed(options)) { |
| clazz.clearPermittedSubclasses(); |
| } |
| } |
| |
| 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(); |
| 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 final Mode mode; |
| |
| Builder(Mode mode) { |
| this.mode = mode; |
| } |
| |
| public boolean isRetainedForFinalTreeShaking(DexAnnotation annotation) { |
| return annotationsToRetain.contains(annotation); |
| } |
| |
| public void retainAnnotation(DexAnnotation annotation) { |
| annotationsToRetain.add(annotation); |
| } |
| |
| public AnnotationRemover build( |
| AppView<AppInfoWithLiveness> appView, Set<DexType> removedClasses) { |
| return new AnnotationRemover(appView, annotationsToRetain, mode, removedClasses); |
| } |
| } |
| } |