| // 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 com.google.common.collect.Sets; | |
| import java.util.List; | |
| import java.util.Set; | |
| import java.util.function.BiConsumer; | |
| public class AndroidApiReferenceLevelCache { | |
| private final AndroidApiLevelCompute apiLevelCompute; | |
| private final AndroidApiLevelDatabase androidApiLevelDatabase; | |
| private final AppView<?> appView; | |
| private final DexItemFactory factory; | |
| // Collection of unknown references attempteed to be looked up. | |
| private final Set<DexReference> unknownReferencesToReport = Sets.newConcurrentHashSet(); | |
| private final boolean reportUnknownReferences; | |
| private AndroidApiReferenceLevelCache( | |
| AppView<?> appView, | |
| AndroidApiLevelCompute apiLevelCompute, | |
| List<AndroidApiForHashingReference> predefinedApiTypeLookupForHashing) { | |
| this.appView = appView; | |
| this.apiLevelCompute = apiLevelCompute; | |
| factory = appView.dexItemFactory(); | |
| androidApiLevelDatabase = | |
| new AndroidApiLevelHashingDatabaseImpl( | |
| predefinedApiTypeLookupForHashing, appView.options(), appView.reporter()); | |
| reportUnknownReferences = appView.options().apiModelingOptions().reportUnknownApiReferences; | |
| } | |
| public static AndroidApiReferenceLevelCache create( | |
| AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) { | |
| assert appView.options().apiModelingOptions().isApiLibraryModelingEnabled(); | |
| 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 Set<DexReference> getUnknownReferencesToReport() { | |
| return unknownReferencesToReport; | |
| } | |
| 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) { | |
| return unknownValue; | |
| } | |
| if (!clazz.isLibraryClass()) { | |
| return appView.computedMinApiLevel(); | |
| } | |
| if (reference.getContextType() == factory.objectType) { | |
| return appView.computedMinApiLevel(); | |
| } | |
| if (appView | |
| .options() | |
| .machineDesugaredLibrarySpecification | |
| .isContextTypeMaintainedOrRewritten(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())) { | |
| AndroidApiLevel typeApiLevel = | |
| androidApiLevelDatabase.getTypeApiLevel(reference.getContextType()); | |
| return typeApiLevel == null | |
| ? appView.computedMinApiLevel() | |
| : apiLevelCompute.of(typeApiLevel); | |
| } | |
| AndroidApiLevel foundApiLevel = | |
| reference.apply( | |
| androidApiLevelDatabase::getTypeApiLevel, | |
| androidApiLevelDatabase::getFieldApiLevel, | |
| androidApiLevelDatabase::getMethodApiLevel); | |
| if (foundApiLevel == null) { | |
| if (reportUnknownReferences) { | |
| unknownReferencesToReport.add(reference); | |
| } | |
| return unknownValue; | |
| } | |
| return apiLevelCompute.of(foundApiLevel.max(appView.options().getMinApiLevel())); | |
| } | |
| } |