[ApiModel] Add predefined api references to api database
For some reason api-versions.xml do not contain StringBuilder.substring at all. This CL ensure that we recognize the api levels for those substring methods and adds infrastructure to add additional references.
Bug: 216587554
Change-Id: I3782f39b851fe052a18bb2527ffeb46018cc52d8
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingClass.java
deleted file mode 100644
index 058201f..0000000
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingClass.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.function.BiConsumer;
-
-/**
- * This is an interface for all generated classes from api-versions.xml for building a database from
- * a serialized hashed format.
- */
-public interface AndroidApiForHashingClass {
-
- DexType getType();
-
- AndroidApiLevel getApiLevel();
-
- void visitMethodsWithApiLevels(BiConsumer<DexMethod, AndroidApiLevel> consumer);
-
- void visitFieldsWithApiLevels(BiConsumer<DexField, AndroidApiLevel> consumer);
-}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingReference.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingReference.java
new file mode 100644
index 0000000..726b1a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiForHashingReference.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2022, 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.DexReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+/** This interface is used to add additional known references to the api database. */
+class AndroidApiForHashingReference {
+
+ private final DexReference reference;
+
+ private final AndroidApiLevel apiLevel;
+
+ private AndroidApiForHashingReference(DexReference reference, AndroidApiLevel apiLevel) {
+ this.reference = reference;
+ this.apiLevel = apiLevel;
+ }
+
+ static AndroidApiForHashingReference create(DexReference reference, AndroidApiLevel apiLevel) {
+ return new AndroidApiForHashingReference(reference, apiLevel);
+ }
+
+ DexReference getReference() {
+ return reference;
+ }
+
+ AndroidApiLevel getApiLevel() {
+ return apiLevel;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
new file mode 100644
index 0000000..486501f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, 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.DexItemFactory;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.function.BiConsumer;
+
+class AndroidApiLevelDatabaseHelper {
+
+ static void visitAdditionalKnownApiReferences(
+ DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
+ // StringBuilder.substring(int) and StringBuilder.substring(int, int) is not part of
+ // api-versions.xml so we add them here. See b/216587554 for related error.
+ apiLevelConsumer.accept(
+ factory.createMethod(
+ factory.stringBuilderType,
+ factory.createProto(factory.stringType, factory.intType),
+ "substring"),
+ AndroidApiLevel.B);
+ apiLevelConsumer.accept(
+ factory.createMethod(
+ factory.stringBuilderType,
+ factory.createProto(factory.stringType, factory.intType, factory.intType),
+ "substring"),
+ AndroidApiLevel.B);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
index bedc254..f5343da 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
@@ -41,24 +41,21 @@
private final Map<DexReference, AndroidApiLevel> ambiguousCache = new IdentityHashMap<>();
public AndroidApiLevelHashingDatabaseImpl(
- List<AndroidApiForHashingClass> predefinedApiTypeLookup) {
+ List<AndroidApiForHashingReference> predefinedApiTypeLookup) {
loadData();
predefinedApiTypeLookup.forEach(
- apiClass -> {
- DexType type = apiClass.getType();
- lookupNonAmbiguousCache.put(type.hashCode(), null);
- ambiguousCache.put(type, apiClass.getApiLevel());
- apiClass.visitMethodsWithApiLevels(
- (method, apiLevel) -> {
- lookupNonAmbiguousCache.put(method.hashCode(), null);
- ambiguousCache.put(method, apiLevel);
- });
- apiClass.visitFieldsWithApiLevels(
- (field, apiLevel) -> {
- lookupNonAmbiguousCache.put(field.hashCode(), null);
- ambiguousCache.put(field, apiLevel);
- });
+ predefinedApiReference -> {
+ int hashCode = predefinedApiReference.getReference().hashCode();
+ // Do not use computeIfAbsent since a return value of null implies the key should not be
+ // inserted.
+ if (!lookupNonAmbiguousCache.containsKey(hashCode)) {
+ lookupNonAmbiguousCache.put(hashCode, null);
+ ambiguousCache.put(
+ predefinedApiReference.getReference(), predefinedApiReference.getApiLevel());
+ }
});
+ assert predefinedApiTypeLookup.stream()
+ .allMatch(added -> added.getApiLevel().isEqualTo(lookupApiLevel(added.getReference())));
}
private void loadData() {
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index b4609ce..2e2aa7a 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -11,8 +11,10 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
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 {
@@ -25,7 +27,7 @@
private AndroidApiReferenceLevelCache(
AppView<?> appView,
AndroidApiLevelCompute apiLevelCompute,
- List<AndroidApiForHashingClass> predefinedApiTypeLookupForHashing) {
+ List<AndroidApiForHashingReference> predefinedApiTypeLookupForHashing) {
this.appView = appView;
this.apiLevelCompute = apiLevelCompute;
factory = appView.dexItemFactory();
@@ -37,11 +39,15 @@
public static AndroidApiReferenceLevelCache create(
AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
assert appView.options().apiModelingOptions().enableApiCallerIdentification;
- ImmutableList.Builder<AndroidApiForHashingClass> builder = ImmutableList.builder();
+ 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(), builder::add);
+ .visitMockedApiLevelsForReferences(appView.dexItemFactory(), addItemToList);
return new AndroidApiReferenceLevelCache(appView, apiLevelCompute, builder.build());
}
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index ae4700f..f9b13a4 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -116,7 +116,7 @@
public ApiReferenceStubber(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
- apiLevelCompute = AndroidApiLevelCompute.create(appView);
+ apiLevelCompute = appView.apiLevelCompute();
desugaredLibraryConfiguration = appView.options().desugaredLibrarySpecification;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
index 4f652cf..4b20c06 100644
--- a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -6,6 +6,7 @@
import java.util.Set;
import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -33,6 +34,11 @@
};
}
+ public static <S, T, R> BiConsumer<S, T> andThen(
+ BiFunction<S, T, R> function, Consumer<R> consumer) {
+ return (s, t) -> consumer.accept(function.apply(s, t));
+ }
+
public static <T> Consumer<T> emptyConsumer() {
return ignore -> {};
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d32fe5c..0b718d8 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.SourceFileProvider;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.Version;
-import com.android.tools.r8.androidapi.AndroidApiForHashingClass;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.dex.Marker;
@@ -39,12 +38,12 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
@@ -1489,12 +1488,6 @@
public static class ApiModelTestingOptions {
- // A mapping from references to the api-level introducing them.
- public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
- public Map<FieldReference, AndroidApiLevel> fieldApiMapping = new HashMap<>();
- public Map<ClassReference, AndroidApiLevel> classApiMapping = new HashMap<>();
- public BiConsumer<MethodReference, ComputedApiLevel> tracedMethodApiLevelCallback = null;
-
public boolean enableApiCallerIdentification =
System.getProperty("com.android.tools.r8.disableApiModeling") == null;
public boolean checkAllApiReferencesAreSet =
@@ -1504,54 +1497,28 @@
public boolean enableOutliningOfMethods =
System.getProperty("com.android.tools.r8.disableApiModeling") == null;
+ // A mapping from references to the api-level introducing them.
+ public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
+ public Map<FieldReference, AndroidApiLevel> fieldApiMapping = new HashMap<>();
+ public Map<ClassReference, AndroidApiLevel> classApiMapping = new HashMap<>();
+ public BiConsumer<MethodReference, ComputedApiLevel> tracedMethodApiLevelCallback = null;
+
public void visitMockedApiLevelsForReferences(
- DexItemFactory factory, Consumer<AndroidApiForHashingClass> consumer) {
+ DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
if (methodApiMapping.isEmpty() && fieldApiMapping.isEmpty() && classApiMapping.isEmpty()) {
return;
}
- Set<ClassReference> classReferences = new HashSet<>(classApiMapping.keySet());
- methodApiMapping
- .keySet()
- .forEach(methodReference -> classReferences.add(methodReference.getHolderClass()));
- fieldApiMapping
- .keySet()
- .forEach(methodReference -> classReferences.add(methodReference.getHolderClass()));
- classReferences.forEach(
- classReference -> {
- consumer.accept(
- new AndroidApiForHashingClass() {
- @Override
- public DexType getType() {
- return factory.createType(classReference.getDescriptor());
- }
-
- @Override
- public AndroidApiLevel getApiLevel() {
- return classApiMapping.getOrDefault(classReference, ANDROID_PLATFORM);
- }
-
- @Override
- public void visitMethodsWithApiLevels(
- BiConsumer<DexMethod, AndroidApiLevel> consumer) {
- methodApiMapping.forEach(
- (methodReference, apiLevel) -> {
- if (methodReference.getHolderClass().equals(classReference)) {
- consumer.accept(factory.createMethod(methodReference), apiLevel);
- }
- });
- }
-
- @Override
- public void visitFieldsWithApiLevels(
- BiConsumer<DexField, AndroidApiLevel> consumer) {
- fieldApiMapping.forEach(
- (fieldReference, apiLevel) -> {
- if (fieldReference.getHolderClass().equals(classReference)) {
- consumer.accept(factory.createField(fieldReference), apiLevel);
- }
- });
- }
- });
+ classApiMapping.forEach(
+ (classReference, apiLevel) -> {
+ apiLevelConsumer.accept(factory.createType(classReference.getDescriptor()), apiLevel);
+ });
+ fieldApiMapping.forEach(
+ (fieldReference, apiLevel) -> {
+ apiLevelConsumer.accept(factory.createField(fieldReference), apiLevel);
+ });
+ methodApiMapping.forEach(
+ (methodReference, apiLevel) -> {
+ apiLevelConsumer.accept(factory.createMethod(methodReference), apiLevel);
});
}