blob: 3c1cfd62c8be1803beadfd507e1a0090ddee352e [file] [log] [blame]
// Copyright (c) 2023, 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.ir.desugar.desugaredlibrary.lint;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class SupportedClasses {
private final Map<DexType, SupportedClass> supportedClasses;
public void forEachClass(Consumer<SupportedClass> consumer) {
supportedClasses.values().forEach(consumer);
}
SupportedClasses(Map<DexType, SupportedClass> supportedClasses) {
this.supportedClasses = supportedClasses;
}
public static class SupportedClass {
private final DexClass clazz;
private final ClassAnnotation classAnnotation;
private final List<DexEncodedMethod> supportedMethods;
private final Map<DexMethod, MethodAnnotation> methodAnnotations;
private SupportedClass(
DexClass clazz,
ClassAnnotation classAnnotation,
List<DexEncodedMethod> supportedMethods,
Map<DexMethod, MethodAnnotation> methodAnnotations) {
this.clazz = clazz;
this.classAnnotation = classAnnotation;
this.supportedMethods = supportedMethods;
this.methodAnnotations = methodAnnotations;
}
public DexType getType() {
return clazz.type;
}
public DexClass getClazz() {
return clazz;
}
public ClassAnnotation getClassAnnotation() {
return classAnnotation;
}
public List<DexEncodedMethod> getSupportedMethods() {
return supportedMethods;
}
public void forEachMethodAndAnnotation(
BiConsumer<DexEncodedMethod, MethodAnnotation> biConsumer) {
for (DexEncodedMethod supportedMethod : supportedMethods) {
biConsumer.accept(supportedMethod, getMethodAnnotation(supportedMethod.getReference()));
}
}
public MethodAnnotation getMethodAnnotation(DexMethod method) {
return methodAnnotations.get(method);
}
static Builder builder(DexClass clazz) {
return new Builder(clazz);
}
private static class Builder {
private final DexClass clazz;
private ClassAnnotation classAnnotation;
private final List<DexEncodedMethod> supportedMethods = new ArrayList<>();
private final Map<DexMethod, MethodAnnotation> methodAnnotations = new HashMap<>();
private Builder(DexClass clazz) {
this.clazz = clazz;
}
void forEachMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
biConsumer.accept(clazz, supportedMethods);
}
void forEachMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
for (DexEncodedMethod dexEncodedMethod : supportedMethods) {
biConsumer.accept(clazz, dexEncodedMethod);
}
}
void addSupportedMethod(DexEncodedMethod method) {
assert method.getHolderType() == clazz.type;
supportedMethods.add(method);
}
void annotateClass(ClassAnnotation annotation) {
assert annotation != null;
assert classAnnotation == null;
classAnnotation = annotation;
}
void annotateMethod(DexMethod method, MethodAnnotation annotation) {
assert method.getHolderType() == clazz.type;
MethodAnnotation prev =
methodAnnotations.getOrDefault(method, MethodAnnotation.getDefault());
methodAnnotations.put(method, annotation.combine(prev));
}
MethodAnnotation getMethodAnnotation(DexMethod method) {
return methodAnnotations.get(method);
}
SupportedClass build() {
supportedMethods.sort(Comparator.comparing(DexEncodedMethod::getReference));
return new SupportedClass(
clazz, classAnnotation, ImmutableList.copyOf(supportedMethods), methodAnnotations);
}
}
}
static Builder builder() {
return new Builder();
}
static class Builder {
Map<DexType, SupportedClass.Builder> supportedClassBuilders = new IdentityHashMap<>();
void forEachClassAndMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
supportedClassBuilders
.values()
.forEach(classBuilder -> classBuilder.forEachMethods(biConsumer));
}
void forEachClassAndMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
supportedClassBuilders
.values()
.forEach(classBuilder -> classBuilder.forEachMethod(biConsumer));
}
void addSupportedMethod(DexClass holder, DexEncodedMethod method) {
SupportedClass.Builder classBuilder =
supportedClassBuilders.computeIfAbsent(
holder.type, clazz -> SupportedClass.builder(holder));
classBuilder.addSupportedMethod(method);
}
void annotateClass(DexType type, ClassAnnotation annotation) {
SupportedClass.Builder classBuilder = supportedClassBuilders.get(type);
assert classBuilder != null;
classBuilder.annotateClass(annotation);
}
void annotateMethod(DexMethod method, MethodAnnotation annotation) {
SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
assert classBuilder != null;
classBuilder.annotateMethod(method, annotation);
}
void annotateMethodIfPresent(DexMethod method, MethodAnnotation annotation) {
SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
if (classBuilder == null) {
return;
}
annotateMethod(method, annotation);
}
MethodAnnotation getMethodAnnotation(DexMethod method) {
SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
assert classBuilder != null;
return classBuilder.getMethodAnnotation(method);
}
SupportedClasses build() {
Map<DexType, SupportedClass> map = new IdentityHashMap<>();
supportedClassBuilders.forEach(
(type, classBuilder) -> {
map.put(type, classBuilder.build());
});
return new SupportedClasses(ImmutableSortedMap.copyOf(map));
}
}
static class ClassAnnotation {
private final boolean fullySupported;
// Methods in latest android.jar but unsupported.
private final List<DexMethod> unsupportedMethods;
public ClassAnnotation(boolean fullySupported, List<DexMethod> unsupportedMethods) {
this.fullySupported = fullySupported;
unsupportedMethods.sort(Comparator.naturalOrder());
this.unsupportedMethods = ImmutableList.copyOf(unsupportedMethods);
}
public boolean isFullySupported() {
return fullySupported;
}
public List<DexMethod> getUnsupportedMethods() {
return unsupportedMethods;
}
}
public static class MethodAnnotation {
private static final MethodAnnotation COVARIANT_RETURN_SUPPORTED =
new MethodAnnotation(false, false, true, false, -1, -1);
private static final MethodAnnotation DEFAULT =
new MethodAnnotation(false, false, false, false, -1, -1);
private static final MethodAnnotation PARALLEL_STREAM_METHOD =
new MethodAnnotation(true, false, false, false, -1, -1);
private static final MethodAnnotation MISSING_FROM_LATEST_ANDROID_JAR =
new MethodAnnotation(false, true, false, false, -1, -1);
// ParallelStream methods are not supported when the runtime api level is strictly below 21.
final boolean parallelStreamMethod;
// Methods not in the latest android jar but still fully supported.
final boolean missingFromLatestAndroidJar;
// Methods not supported in a given min api range.
final boolean unsupportedInMinApiRange;
final boolean covariantReturnSupported;
final int minRange;
final int maxRange;
MethodAnnotation(
boolean parallelStreamMethod,
boolean missingFromLatestAndroidJar,
boolean covariantReturnSupported,
boolean unsupportedInMinApiRange,
int minRange,
int maxRange) {
this.parallelStreamMethod = parallelStreamMethod;
this.missingFromLatestAndroidJar = missingFromLatestAndroidJar;
this.covariantReturnSupported = covariantReturnSupported;
this.unsupportedInMinApiRange = unsupportedInMinApiRange;
this.minRange = minRange;
this.maxRange = maxRange;
}
public static MethodAnnotation getCovariantReturnSupported() {
return COVARIANT_RETURN_SUPPORTED;
}
public static MethodAnnotation getDefault() {
return DEFAULT;
}
public static MethodAnnotation getParallelStreamMethod() {
return PARALLEL_STREAM_METHOD;
}
public static MethodAnnotation getMissingFromLatestAndroidJar() {
return MISSING_FROM_LATEST_ANDROID_JAR;
}
public static MethodAnnotation createMissingInMinApi(int api) {
return new MethodAnnotation(false, false, false, true, api, api);
}
public boolean isUnsupportedInMinApiRange() {
return unsupportedInMinApiRange;
}
public int getMinRange() {
return minRange;
}
public int getMaxRange() {
return maxRange;
}
public boolean isCovariantReturnSupported() {
return covariantReturnSupported;
}
public MethodAnnotation combine(MethodAnnotation other) {
if (this == getDefault()) {
return other;
}
if (other == getDefault()) {
return this;
}
int newMin, newMax;
if (!unsupportedInMinApiRange && !other.unsupportedInMinApiRange) {
newMin = newMax = -1;
} else if (!unsupportedInMinApiRange || !other.unsupportedInMinApiRange) {
newMin = unsupportedInMinApiRange ? minRange : other.minRange;
newMax = unsupportedInMinApiRange ? maxRange : other.maxRange;
} else {
// Merge ranges if contiguous or throw.
if (maxRange == other.minRange - 1) {
newMin = minRange;
newMax = other.maxRange;
} else if (other.maxRange == minRange - 1) {
newMin = other.minRange;
newMax = maxRange;
} else {
// 20 is missing, so if maxRange or minRange are 19 the following is 21.
if (maxRange == 19 && other.minRange == 21) {
newMin = minRange;
newMax = other.maxRange;
} else if (other.maxRange == 19 && minRange == 21) {
newMin = other.minRange;
newMax = maxRange;
} else {
throw new RuntimeException("Cannot merge ranges.");
}
}
}
return new MethodAnnotation(
parallelStreamMethod || other.parallelStreamMethod,
missingFromLatestAndroidJar || other.missingFromLatestAndroidJar,
covariantReturnSupported || other.covariantReturnSupported,
unsupportedInMinApiRange || other.unsupportedInMinApiRange,
newMin,
newMax);
}
}
}