| // 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.GenericSignatureContextBuilder.TypeParameterContext.empty; |
| |
| import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter; |
| import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; |
| import java.util.Collection; |
| 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 GenericSignatureContextBuilder { |
| |
| 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); |
| } |
| } |
| |
| public 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; |
| } |
| return dead |
| ? addPrunedSubstitutions(information.parametersWithBounds) |
| : addLiveParameters(information.parametersWithBounds.keySet()); |
| } |
| |
| public static TypeParameterContext empty() { |
| return EMPTY; |
| } |
| |
| public boolean isLiveParameter(String parameterName) { |
| return liveParameters.contains(parameterName); |
| } |
| |
| public DexType getPrunedSubstitution(String parameterName) { |
| assert !isLiveParameter(parameterName); |
| return prunedParametersWithBounds.get(parameterName); |
| } |
| |
| public TypeParameterContext addLiveParameters(Collection<String> typeParameters) { |
| if (typeParameters.isEmpty()) { |
| return this; |
| } |
| HashSet<String> newLiveParameters = new HashSet<>(); |
| newLiveParameters.addAll(liveParameters); |
| newLiveParameters.addAll(typeParameters); |
| HashMap<String, DexType> newPruned = new HashMap<>(); |
| prunedParametersWithBounds.forEach( |
| (name, type) -> { |
| if (!typeParameters.contains(name)) { |
| newPruned.put(name, type); |
| } |
| }); |
| return new TypeParameterContext(newPruned, newLiveParameters); |
| } |
| |
| public TypeParameterContext addPrunedSubstitutions(Map<String, DexType> substitutions) { |
| if (substitutions.isEmpty()) { |
| return this; |
| } |
| HashMap<String, DexType> newPruned = new HashMap<>(); |
| newPruned.putAll(prunedParametersWithBounds); |
| newPruned.putAll(substitutions); |
| HashSet<String> newLiveParameters = new HashSet<>(); |
| liveParameters.forEach( |
| name -> { |
| if (!substitutions.containsKey(name)) { |
| newLiveParameters.add(name); |
| } |
| }); |
| return new TypeParameterContext(newPruned, newLiveParameters); |
| } |
| } |
| |
| public static class AlwaysLiveTypeParameterContext extends TypeParameterContext { |
| |
| private AlwaysLiveTypeParameterContext() { |
| super(Collections.emptyMap(), Collections.emptySet()); |
| } |
| |
| public static AlwaysLiveTypeParameterContext create() { |
| return new AlwaysLiveTypeParameterContext(); |
| } |
| |
| @Override |
| public boolean isLiveParameter(String parameterName) { |
| return true; |
| } |
| |
| @Override |
| public DexType getPrunedSubstitution(String parameterName) { |
| assert false; |
| return null; |
| } |
| |
| @Override |
| public TypeParameterContext addLiveParameters(Collection<String> typeParameters) { |
| return this; |
| } |
| |
| @Override |
| public TypeParameterContext addPrunedSubstitutions(Map<String, DexType> substitutions) { |
| return this; |
| } |
| } |
| |
| private GenericSignatureContextBuilder( |
| Map<DexReference, TypeParameterSubstitutions> formalsInfo, |
| Map<DexReference, DexReference> enclosingInfo) { |
| this.formalsInfo = formalsInfo; |
| this.enclosingInfo = enclosingInfo; |
| } |
| |
| public static GenericSignatureContextBuilder create(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 GenericSignatureContextBuilder(formalsInfo, enclosingInfo); |
| } |
| |
| public TypeParameterContext computeTypeParameterContext( |
| AppView<?> appView, DexReference reference, Predicate<DexType> wasPruned) { |
| assert !wasPruned.test(reference.getContextType()); |
| return computeTypeParameterContext(appView, reference, wasPruned, false); |
| } |
| |
| 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(contextType); |
| boolean prunedHere = seenPruned || clazz == null; |
| if (appView.hasLiveness() |
| && appView.withLiveness().appInfo().getMissingClasses().contains(contextType)) { |
| prunedHere = seenPruned; |
| } |
| // Lookup the formals in the enclosing context. |
| TypeParameterContext typeParameterContext = |
| computeTypeParameterContext( |
| appView, |
| enclosingInfo.get(contextType), |
| wasPruned, |
| prunedHere |
| || hasPrunedRelationship( |
| appView, enclosingInfo.get(contextType), contextType, wasPruned)) |
| // Add formals for the context |
| .combine(formalsInfo.get(contextType), prunedHere); |
| if (!reference.isDexMethod()) { |
| return typeParameterContext; |
| } |
| prunedHere = prunedHere || clazz == null || clazz.lookupMethod(reference.asDexMethod()) == null; |
| return typeParameterContext.combine(formalsInfo.get(reference), prunedHere); |
| } |
| |
| public 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; |
| } |
| // TODO(b/187035453): We should visit generic signatures in the enqueuer. |
| DexClass enclosingClass = |
| appView |
| .appInfo() |
| .definitionForWithoutExistenceAssert( |
| appView.graphLens().lookupClassType(enclosingReference.getContextType())); |
| DexClass enclosedClass = |
| appView |
| .appInfo() |
| .definitionForWithoutExistenceAssert( |
| appView.graphLens().lookupClassType(enclosedClassType)); |
| if (enclosingClass == null || enclosedClass == null) { |
| return true; |
| } |
| if (enclosedClass.getEnclosingMethodAttribute() != null) { |
| return enclosingReference.isDexMethod() |
| ? enclosedClass.getEnclosingMethodAttribute().getEnclosingMethod() != enclosingReference |
| : enclosedClass.getEnclosingMethodAttribute().getEnclosingClass() != enclosingReference; |
| } else { |
| InnerClassAttribute innerClassAttribute = enclosedClass.getInnerClassAttributeForThisClass(); |
| return innerClassAttribute == null || innerClassAttribute.getOuter() != enclosingReference; |
| } |
| } |
| |
| public boolean hasGenericTypeVariables( |
| AppView<?> appView, DexType type, Predicate<DexType> wasPruned) { |
| if (wasPruned.test(type)) { |
| return false; |
| } |
| DexClass clazz = appView.definitionFor(appView.graphLens().lookupClassType(type)); |
| if (clazz == null || clazz.isNotProgramClass() || clazz.getClassSignature().isInvalid()) { |
| return true; |
| } |
| return !clazz.getClassSignature().getFormalTypeParameters().isEmpty(); |
| } |
| } |