[ApiModel] Add flag to have unknown library references reported

Change-Id: Ia029077247307244c46883255c8ab2d138df370e
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d57d450..6ef6c416 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -370,6 +370,10 @@
         assert appView.rootSet().verifyKeptItemsAreKept(appView);
         appView.rootSet().checkAllRulesAreUsed(options);
 
+        if (options.apiModelingOptions().reportUnknownApiReferences) {
+          appView.apiLevelCompute().reportUnknownApiReferences();
+        }
+
         if (options.proguardSeedsConsumer != null) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiDiagnostic.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiDiagnostic.java
new file mode 100644
index 0000000..98471f2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiDiagnostic.java
@@ -0,0 +1,10 @@
+// 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.Diagnostic;
+
+/** Base class for api related diagnostics. */
+public abstract class AndroidApiDiagnostic implements Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
index 1c61851..5a0bd81 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.androidapi;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.androidapi.ComputedApiLevel.KnownApiLevel;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -44,6 +45,10 @@
 
   public abstract boolean isEnabled();
 
+  public void reportUnknownApiReferences() {
+    // Do nothing here.
+  }
+
   public ComputedApiLevel computeApiLevelForDefinition(
       DexMember<?, ?> reference, DexItemFactory factory, ComputedApiLevel unknownValue) {
     return computeApiLevelForDefinition(reference.getReferencedBaseTypes(factory), unknownValue);
@@ -105,10 +110,12 @@
 
     private final AndroidApiReferenceLevelCache cache;
     private final ComputedApiLevel minApiLevel;
+    private final DiagnosticsHandler diagnosticsHandler;
 
     public DefaultAndroidApiLevelCompute(AppView<?> appView) {
       this.cache = AndroidApiReferenceLevelCache.create(appView, this);
       this.minApiLevel = of(appView.options().getMinApiLevel());
+      this.diagnosticsHandler = appView.reporter();
     }
 
     @Override
@@ -131,5 +138,14 @@
         DexReference reference, ComputedApiLevel unknownValue) {
       return cache.lookup(reference, unknownValue);
     }
+
+    @Override
+    public void reportUnknownApiReferences() {
+      cache
+          .getUnknownReferencesToReport()
+          .forEach(
+              reference ->
+                  diagnosticsHandler.warning(new AndroidApiUnknownReferenceDiagnostic(reference)));
+    }
   }
 }
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 c4d2d5a..0e267e5 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -12,7 +12,9 @@
 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 {
@@ -22,6 +24,10 @@
   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,
@@ -32,6 +38,7 @@
     androidApiLevelDatabase =
         new AndroidApiLevelHashingDatabaseImpl(
             predefinedApiTypeLookupForHashing, appView.options(), appView.reporter());
+    reportUnknownReferences = appView.options().apiModelingOptions().reportUnknownApiReferences;
   }
 
   public static AndroidApiReferenceLevelCache create(
@@ -49,6 +56,10 @@
     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();
@@ -98,8 +109,12 @@
             androidApiLevelDatabase::getTypeApiLevel,
             androidApiLevelDatabase::getFieldApiLevel,
             androidApiLevelDatabase::getMethodApiLevel);
-    return (foundApiLevel == null)
-        ? unknownValue
-        : apiLevelCompute.of(foundApiLevel.max(appView.options().getMinApiLevel()));
+    if (foundApiLevel == null) {
+      if (reportUnknownReferences) {
+        unknownReferencesToReport.add(reference);
+      }
+      return unknownValue;
+    }
+    return apiLevelCompute.of(foundApiLevel.max(appView.options().getMinApiLevel()));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiUnknownReferenceDiagnostic.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiUnknownReferenceDiagnostic.java
new file mode 100644
index 0000000..fba87df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiUnknownReferenceDiagnostic.java
@@ -0,0 +1,35 @@
+// 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.Keep;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class AndroidApiUnknownReferenceDiagnostic extends AndroidApiDiagnostic {
+
+  private final DexReference reference;
+
+  AndroidApiUnknownReferenceDiagnostic(DexReference reference) {
+    this.reference = reference;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return reference.toSourceString() + " cannot be found in the api database.";
+  }
+}
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 7571ef2..16ec417 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1764,6 +1764,8 @@
         System.getProperty("com.android.tools.r8.disableApiModeling") == null;
     public boolean enableOutliningOfMethods =
         System.getProperty("com.android.tools.r8.disableApiModeling") == null;
+    public boolean reportUnknownApiReferences =
+        System.getProperty("com.android.tools.r8.reportUnknownApiReferences") != null;
 
     // TODO(b/232823652): Enable when we can compute the offset correctly.
     public boolean useMemoryMappedByteBuffer = false;