| // 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.graph; |
| |
| import static com.android.tools.r8.graph.GenericSignatureTypeVariableRemover.TypeParameterContext.empty; |
| import static com.google.common.base.Predicates.alwaysFalse; |
| |
| import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter; |
| import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Predicate; |
| |
| public class GenericSignatureTypeVariableRemover { |
| |
| private final DexType objectType; |
| private final Map<DexReference, TypeParameterSubstitutions> formalsInfo; |
| private final Map<DexReference, DexReference> enclosingInfo; |
| |
| private static class TypeParameterSubstitutions { |
| |
| private final Map<String, DexType> parametersWithBounds; |
| |
| private TypeParameterSubstitutions(Map<String, DexType> parametersWithBounds) { |
| this.parametersWithBounds = parametersWithBounds; |
| } |
| |
| private static TypeParameterSubstitutions create(List<FormalTypeParameter> formals) { |
| Map<String, DexType> map = new IdentityHashMap<>(); |
| formals.forEach( |
| formal -> { |
| DexType bound = null; |
| if (formal.getClassBound() != null |
| && formal.getClassBound().hasSignature() |
| && formal.getClassBound().isClassTypeSignature()) { |
| bound = formal.getClassBound().asClassTypeSignature().type; |
| } else if (!formal.getInterfaceBounds().isEmpty() |
| && formal.getInterfaceBounds().get(0).isClassTypeSignature()) { |
| bound = formal.getInterfaceBounds().get(0).asClassTypeSignature().type; |
| } |
| map.put(formal.getName(), bound); |
| }); |
| return new TypeParameterSubstitutions(map); |
| } |
| } |
| |
| static class TypeParameterContext { |
| |
| private static final TypeParameterContext EMPTY = |
| new TypeParameterContext(Collections.emptyMap(), Collections.emptySet()); |
| |
| private final Map<String, DexType> prunedParametersWithBounds; |
| private final Set<String> liveParameters; |
| |
| private TypeParameterContext( |
| Map<String, DexType> prunedParametersWithBounds, Set<String> liveParameters) { |
| this.prunedParametersWithBounds = prunedParametersWithBounds; |
| this.liveParameters = liveParameters; |
| } |
| |
| private TypeParameterContext combine(TypeParameterSubstitutions information, boolean dead) { |
| if (information == null) { |
| return this; |
| } |
| HashMap<String, DexType> newPruned = new HashMap<>(prunedParametersWithBounds); |
| HashSet<String> newLiveParameters = new HashSet<>(liveParameters); |
| information.parametersWithBounds.forEach( |
| (param, type) -> { |
| if (dead) { |
| newPruned.put(param, type); |
| newLiveParameters.remove(param); |
| } else { |
| newLiveParameters.add(param); |
| newPruned.remove(param); |
| } |
| }); |
| return new TypeParameterContext(newPruned, newLiveParameters); |
| } |
| |
| public static TypeParameterContext empty() { |
| return EMPTY; |
| } |
| } |
| |
| private GenericSignatureTypeVariableRemover( |
| Map<DexReference, TypeParameterSubstitutions> formalsInfo, |
| Map<DexReference, DexReference> enclosingInfo, |
| DexType objectType) { |
| this.formalsInfo = formalsInfo; |
| this.enclosingInfo = enclosingInfo; |
| this.objectType = objectType; |
| } |
| |
| public static GenericSignatureTypeVariableRemover create( |
| AppView<?> appView, List<DexProgramClass> programClasses) { |
| Map<DexReference, TypeParameterSubstitutions> formalsInfo = new IdentityHashMap<>(); |
| Map<DexReference, DexReference> enclosingInfo = new IdentityHashMap<>(); |
| programClasses.forEach( |
| clazz -> { |
| // Build up a map of type variables to bounds for every reference such that we can |
| // lookup the information even after we prune the generic signatures. |
| if (clazz.getClassSignature().isValid()) { |
| formalsInfo.put( |
| clazz.getReference(), |
| TypeParameterSubstitutions.create(clazz.classSignature.getFormalTypeParameters())); |
| clazz.forEachProgramMethod( |
| method -> { |
| MethodTypeSignature methodSignature = |
| method.getDefinition().getGenericSignature(); |
| if (methodSignature.isValid()) { |
| formalsInfo.put( |
| method.getReference(), |
| TypeParameterSubstitutions.create( |
| methodSignature.getFormalTypeParameters())); |
| } |
| }); |
| } |
| // Build up an enclosing class context such that the enclosing class can be looked up |
| // even after inner class and enclosing method attribute attributes are removed. |
| InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass(); |
| if (innerClassAttribute != null) { |
| enclosingInfo.put(clazz.getType(), innerClassAttribute.getOuter()); |
| } |
| EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute(); |
| if (enclosingMethodAttribute != null) { |
| enclosingInfo.put( |
| clazz.getType(), |
| enclosingMethodAttribute.getEnclosingMethod() != null |
| ? enclosingMethodAttribute.getEnclosingMethod() |
| : enclosingMethodAttribute.getEnclosingClass()); |
| } |
| }); |
| return new GenericSignatureTypeVariableRemover( |
| formalsInfo, enclosingInfo, appView.dexItemFactory().objectType); |
| } |
| |
| private TypeParameterContext computeTypeParameterContext( |
| AppView<?> appView, |
| DexReference reference, |
| Predicate<DexType> wasPruned, |
| boolean seenPruned) { |
| if (reference == null) { |
| return empty(); |
| } |
| DexType contextType = reference.getContextType(); |
| // TODO(b/187035453): We should visit generic signatures in the enqueuer. |
| DexClass clazz = |
| appView.appInfo().definitionForWithoutExistenceAssert(reference.getContextType()); |
| boolean prunedHere = seenPruned || clazz == null; |
| if (appView.hasLiveness() |
| && appView |
| .withLiveness() |
| .appInfo() |
| .getMissingClasses() |
| .contains(reference.getContextType())) { |
| prunedHere = seenPruned; |
| } |
| if (reference.isDexMethod()) { |
| TypeParameterSubstitutions typeParameterSubstitutions = formalsInfo.get(reference); |
| if (clazz != null) { |
| assert clazz.isProgramClass(); |
| DexEncodedMethod method = clazz.lookupMethod(reference.asDexMethod()); |
| if (method == null) { |
| prunedHere = true; |
| } else if (method.isStatic()) { |
| // Static methods define their own scope. |
| return empty().combine(typeParameterSubstitutions, seenPruned); |
| } |
| } |
| // Lookup the formals in the enclosing context. |
| return computeTypeParameterContext( |
| appView, |
| contextType, |
| wasPruned, |
| prunedHere |
| || hasPrunedRelationship( |
| appView, enclosingInfo.get(contextType), contextType, wasPruned)) |
| // Add the formals for the class. |
| .combine(formalsInfo.get(contextType), prunedHere) |
| // Add the formals for the method. |
| .combine(formalsInfo.get(reference), prunedHere); |
| } |
| assert reference.isDexType(); |
| return computeTypeParameterContext( |
| appView, |
| enclosingInfo.get(reference), |
| wasPruned, |
| prunedHere |
| || hasPrunedRelationship( |
| appView, enclosingInfo.get(reference), contextType, wasPruned)) |
| .combine(formalsInfo.get(reference), prunedHere); |
| } |
| |
| private static boolean hasPrunedRelationship( |
| AppView<?> appView, |
| DexReference enclosingReference, |
| DexType enclosedClassType, |
| Predicate<DexType> wasPruned) { |
| assert enclosedClassType != null; |
| if (enclosingReference == null) { |
| // There is no relationship, so it does not really matter what we return since the |
| // algorithm will return the base case. |
| return true; |
| } |
| if (wasPruned.test(enclosingReference.getContextType()) || wasPruned.test(enclosedClassType)) { |
| return true; |
| } |
| DexClass enclosingClass = appView.definitionFor(enclosingReference.getContextType()); |
| DexClass enclosedClass = appView.definitionFor(enclosedClassType); |
| if (enclosingClass == null || enclosedClass == null) { |
| return true; |
| } |
| if (enclosingReference.isDexMethod()) { |
| return enclosedClass.getEnclosingMethodAttribute() == null |
| || enclosedClass.getEnclosingMethodAttribute().getEnclosingMethod() != enclosingReference; |
| } else { |
| InnerClassAttribute innerClassAttribute = enclosedClass.getInnerClassAttributeForThisClass(); |
| if (innerClassAttribute != null) { |
| return innerClassAttribute.getOuter() != enclosingReference; |
| } |
| return enclosedClass.getEnclosingMethodAttribute() == null |
| || enclosedClass.getEnclosingMethodAttribute().getEnclosingClass() != enclosingReference; |
| } |
| } |
| |
| private static boolean hasGenericTypeVariables( |
| AppView<?> appView, DexType type, Predicate<DexType> wasPruned) { |
| if (wasPruned.test(type)) { |
| return false; |
| } |
| DexClass clazz = appView.definitionFor(type); |
| if (clazz == null || clazz.isNotProgramClass() || clazz.getClassSignature().isInvalid()) { |
| return true; |
| } |
| return !clazz.getClassSignature().getFormalTypeParameters().isEmpty(); |
| } |
| |
| public void removeDeadGenericSignatureTypeVariables(AppView<?> appView) { |
| Predicate<DexType> wasPruned = |
| appView.hasLiveness() ? appView.withLiveness().appInfo()::wasPruned : alwaysFalse(); |
| GenericSignaturePartialTypeArgumentApplier baseArgumentApplier = |
| GenericSignaturePartialTypeArgumentApplier.build( |
| objectType, |
| (enclosing, enclosed) -> hasPrunedRelationship(appView, enclosing, enclosed, wasPruned), |
| type -> hasGenericTypeVariables(appView, type, wasPruned)); |
| appView |
| .appInfo() |
| .classes() |
| .forEach( |
| clazz -> { |
| if (clazz.getClassSignature().isInvalid()) { |
| return; |
| } |
| TypeParameterContext computedClassFormals = |
| computeTypeParameterContext(appView, clazz.getType(), wasPruned, false); |
| GenericSignaturePartialTypeArgumentApplier classArgumentApplier = |
| baseArgumentApplier.addSubstitutionsAndVariables( |
| computedClassFormals.prunedParametersWithBounds, |
| computedClassFormals.liveParameters); |
| clazz.setClassSignature( |
| classArgumentApplier.visitClassSignature(clazz.getClassSignature())); |
| clazz |
| .methods() |
| .forEach( |
| method -> { |
| MethodTypeSignature methodSignature = method.getGenericSignature(); |
| if (methodSignature.hasSignature() |
| && method.getGenericSignature().isValid()) { |
| if (method.isStatic()) { |
| method.setGenericSignature( |
| baseArgumentApplier |
| .buildForMethod(methodSignature.getFormalTypeParameters()) |
| .visitMethodSignature(methodSignature)); |
| } else { |
| method.setGenericSignature( |
| classArgumentApplier |
| .buildForMethod(methodSignature.getFormalTypeParameters()) |
| .visitMethodSignature(methodSignature)); |
| } |
| } |
| }); |
| clazz |
| .instanceFields() |
| .forEach( |
| field -> { |
| if (field.getGenericSignature().hasSignature() |
| && field.getGenericSignature().isValid()) { |
| field.setGenericSignature( |
| classArgumentApplier.visitFieldTypeSignature( |
| field.getGenericSignature())); |
| } |
| }); |
| }); |
| } |
| } |