blob: 00534ba166d21521fcb0fc27bab9e40772c835b5 [file] [log] [blame]
// 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();
}
}