blob: cce085261c654e2afd39e84fcee96bdf722feacd [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.androidapi;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ConsumerUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.function.BiConsumer;
public class AndroidApiReferenceLevelCache {
private final AndroidApiLevelCompute apiLevelCompute;
private final AndroidApiLevelDatabase androidApiLevelDatabase;
private final AppView<?> appView;
private final DexItemFactory factory;
private AndroidApiReferenceLevelCache(
AppView<?> appView,
AndroidApiLevelCompute apiLevelCompute,
List<AndroidApiForHashingReference> predefinedApiTypeLookupForHashing) {
this.appView = appView;
this.apiLevelCompute = apiLevelCompute;
factory = appView.dexItemFactory();
androidApiLevelDatabase =
new AndroidApiLevelHashingDatabaseImpl(predefinedApiTypeLookupForHashing);
}
public static AndroidApiReferenceLevelCache create(
AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
assert appView.options().apiModelingOptions().enableApiCallerIdentification;
ImmutableList.Builder<AndroidApiForHashingReference> builder = ImmutableList.builder();
BiConsumer<DexReference, AndroidApiLevel> addItemToList =
ConsumerUtils.andThen(AndroidApiForHashingReference::create, builder::add);
AndroidApiLevelDatabaseHelper.visitAdditionalKnownApiReferences(
appView.dexItemFactory(), addItemToList);
appView
.options()
.apiModelingOptions()
.visitMockedApiLevelsForReferences(appView.dexItemFactory(), addItemToList);
return new AndroidApiReferenceLevelCache(appView, apiLevelCompute, builder.build());
}
public ComputedApiLevel lookupMax(
DexReference reference, ComputedApiLevel minApiLevel, ComputedApiLevel unknownValue) {
assert !minApiLevel.isNotSetApiLevel();
return lookup(reference, unknownValue).max(minApiLevel);
}
public ComputedApiLevel lookup(DexReference reference, ComputedApiLevel unknownValue) {
DexType contextType = reference.getContextType();
if (contextType.isArrayType()) {
if (reference.isDexMethod() && reference.asDexMethod().match(factory.objectMembers.clone)) {
return appView.computedMinApiLevel();
}
return lookup(contextType.toBaseType(factory), unknownValue);
}
if (contextType.isPrimitiveType() || contextType.isVoidType()) {
return appView.computedMinApiLevel();
}
DexClass clazz = appView.definitionFor(contextType);
if (clazz != null && clazz.isProgramClass()) {
return appView.computedMinApiLevel();
}
if (reference.getContextType() == factory.objectType) {
return appView.computedMinApiLevel();
}
if (appView.options().machineDesugaredLibrarySpecification.isSupported(reference)) {
// If we end up desugaring the reference, the library classes is bridged by j$ which is part
// of the program.
return appView.computedMinApiLevel();
}
if (reference.isDexMethod()
&& !reference.asDexMethod().isInstanceInitializer(factory)
&& factory.objectMembers.isObjectMember(reference.asDexMethod())) {
// If we can lookup the method it was introduced/overwritten later. Take for example
// a default toString that was not available before some api level. If unknown we default
// back to the static holder.
AndroidApiLevel methodApiLevel =
androidApiLevelDatabase.getMethodApiLevel(reference.asDexMethod());
if (methodApiLevel != null) {
return apiLevelCompute.of(methodApiLevel);
}
AndroidApiLevel typeApiLevel =
androidApiLevelDatabase.getTypeApiLevel(reference.getContextType());
// TODO(b/207452750): Investigate if we can return minApi here.
return typeApiLevel == null ? ComputedApiLevel.unknown() : apiLevelCompute.of(typeApiLevel);
}
AndroidApiLevel foundApiLevel =
reference.apply(
androidApiLevelDatabase::getTypeApiLevel,
androidApiLevelDatabase::getFieldApiLevel,
androidApiLevelDatabase::getMethodApiLevel);
return (foundApiLevel == null)
? unknownValue
: apiLevelCompute.of(foundApiLevel.max(appView.options().getMinApiLevel()));
}
}