[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);
           });
     }