Warn when startup items are not in program input

Change-Id: I9abcaf92f84c4b64bc627a43fe8b8a819327d970
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 55b4e69..3bd5968 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.experimental.startup.StartupCompleteness;
 import com.android.tools.r8.experimental.startup.StartupOrder;
-import com.android.tools.r8.experimental.startup.profile.art.ARTProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
@@ -226,8 +225,7 @@
       StartupOrder startupOrder =
           appView.appInfo().hasClassHierarchy()
               ? appView.appInfoWithClassHierarchy().getStartupOrder()
-              : StartupOrder.createInitialStartupOrder(
-                  options, SyntheticToSyntheticContextGeneralization.createForD8());
+              : StartupOrder.createInitialStartupOrderForD8(appView);
       distributor =
           new VirtualFile.FillFilesDistributor(
               this, classes, options, executorService, startupOrder);
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
index 9a44961..291e8f2 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
@@ -37,7 +37,7 @@
         appView.hasClassHierarchy()
             ? appView.appInfoWithClassHierarchy().getStartupOrder()
             : StartupOrder.createInitialStartupOrder(
-                appView.options(), syntheticToSyntheticContextGeneralization);
+                appView.options(), null, syntheticToSyntheticContextGeneralization);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
index 5fba5e9..d80396d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.experimental.startup.profile.StartupProfile;
 import com.android.tools.r8.experimental.startup.profile.art.ARTProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
@@ -22,15 +24,27 @@
 
   public static StartupOrder createInitialStartupOrder(
       InternalOptions options,
+      DexDefinitionSupplier definitions,
       SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
     StartupProfile startupProfile =
-        StartupProfile.parseStartupProfile(options, syntheticToSyntheticContextGeneralization);
+        StartupProfile.parseStartupProfile(
+            options, definitions, syntheticToSyntheticContextGeneralization);
     if (startupProfile == null || startupProfile.getStartupItems().isEmpty()) {
       return empty();
     }
     return new NonEmptyStartupOrder(new LinkedHashSet<>(startupProfile.getStartupItems()));
   }
 
+  public static StartupOrder createInitialStartupOrderForD8(AppView<?> appView) {
+    return createInitialStartupOrder(
+        appView.options(), appView, SyntheticToSyntheticContextGeneralization.createForD8());
+  }
+
+  public static StartupOrder createInitialStartupOrderForR8(DexApplication application) {
+    return createInitialStartupOrder(
+        application.options, application, SyntheticToSyntheticContextGeneralization.createForR8());
+  }
+
   public static StartupOrder empty() {
     return new EmptyStartupOrder();
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
index f23f459..842e181 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.experimental.startup.profile.art.ARTProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 import com.android.tools.r8.experimental.startup.profile.art.AlwaysTrueARTProfileRulePredicate;
 import com.android.tools.r8.experimental.startup.profile.art.HumanReadableARTProfileParser;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.startup.ARTProfileRulePredicate;
 import com.android.tools.r8.startup.HumanReadableARTProfileParserBuilder;
@@ -17,6 +18,8 @@
 import com.android.tools.r8.startup.StartupProfileBuilder;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.startup.SyntheticStartupMethodBuilder;
+import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
+import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic.Builder;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ArrayList;
@@ -35,9 +38,14 @@
 
   public static Builder builder(
       InternalOptions options,
+      MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder,
       StartupProfileProvider startupProfileProvider,
       SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
-    return new Builder(options, startupProfileProvider, syntheticToSyntheticContextGeneralization);
+    return new Builder(
+        options,
+        missingItemsDiagnosticBuilder,
+        startupProfileProvider,
+        syntheticToSyntheticContextGeneralization);
   }
 
   public static StartupProfile merge(Collection<StartupProfile> startupProfiles) {
@@ -65,6 +73,7 @@
    */
   public static StartupProfile parseStartupProfile(
       InternalOptions options,
+      DexDefinitionSupplier definitions,
       SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
     if (!options.getStartupOptions().hasStartupProfileProviders()) {
       return null;
@@ -73,11 +82,20 @@
         options.getStartupOptions().getStartupProfileProviders();
     List<StartupProfile> startupProfiles = new ArrayList<>(startupProfileProviders.size());
     for (StartupProfileProvider startupProfileProvider : startupProfileProviders) {
+      MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder =
+          new MissingStartupProfileItemsDiagnostic.Builder(definitions)
+              .setOrigin(startupProfileProvider.getOrigin());
       StartupProfile.Builder startupProfileBuilder =
           StartupProfile.builder(
-              options, startupProfileProvider, syntheticToSyntheticContextGeneralization);
+              options,
+              missingItemsDiagnosticBuilder,
+              startupProfileProvider,
+              syntheticToSyntheticContextGeneralization);
       startupProfileProvider.getStartupProfile(startupProfileBuilder);
       startupProfiles.add(startupProfileBuilder.build());
+      if (missingItemsDiagnosticBuilder.hasMissingStartupItems()) {
+        options.reporter.warning(missingItemsDiagnosticBuilder.build());
+      }
     }
     return StartupProfile.merge(startupProfiles);
   }
@@ -89,6 +107,7 @@
   public static class Builder implements StartupProfileBuilder {
 
     private final DexItemFactory dexItemFactory;
+    private final MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder;
     private final InternalOptions options;
     private final StartupProfileProvider startupProfileProvider;
     private final SyntheticToSyntheticContextGeneralization
@@ -98,9 +117,11 @@
 
     Builder(
         InternalOptions options,
+        MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder,
         StartupProfileProvider startupProfileProvider,
         SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization) {
       this.dexItemFactory = options.dexItemFactory();
+      this.missingItemsDiagnosticBuilder = missingItemsDiagnosticBuilder;
       this.options = options;
       this.startupProfileProvider = startupProfileProvider;
       this.syntheticToSyntheticContextGeneralization = syntheticToSyntheticContextGeneralization;
@@ -110,14 +131,22 @@
     public Builder addStartupClass(Consumer<StartupClassBuilder> startupClassBuilderConsumer) {
       StartupClass.Builder startupClassBuilder = StartupClass.builder(dexItemFactory);
       startupClassBuilderConsumer.accept(startupClassBuilder);
-      return addStartupItem(startupClassBuilder.build());
+      StartupClass startupClass = startupClassBuilder.build();
+      if (missingItemsDiagnosticBuilder.registerStartupClass(startupClass)) {
+        return this;
+      }
+      return addStartupItem(startupClass);
     }
 
     @Override
     public Builder addStartupMethod(Consumer<StartupMethodBuilder> startupMethodBuilderConsumer) {
       StartupMethod.Builder startupMethodBuilder = StartupMethod.builder(dexItemFactory);
       startupMethodBuilderConsumer.accept(startupMethodBuilder);
-      return addStartupItem(startupMethodBuilder.build());
+      StartupMethod startupMethod = startupMethodBuilder.build();
+      if (missingItemsDiagnosticBuilder.registerStartupMethod(startupMethod)) {
+        return this;
+      }
+      return addStartupItem(startupMethod);
     }
 
     @Override
@@ -126,7 +155,11 @@
       SyntheticStartupMethod.Builder syntheticStartupMethodBuilder =
           SyntheticStartupMethod.builder(dexItemFactory);
       syntheticStartupMethodBuilderConsumer.accept(syntheticStartupMethodBuilder);
-      return addStartupItem(syntheticStartupMethodBuilder.build());
+      SyntheticStartupMethod syntheticStartupMethod = syntheticStartupMethodBuilder.build();
+      if (missingItemsDiagnosticBuilder.registerSyntheticStartupMethod(syntheticStartupMethod)) {
+        return this;
+      }
+      return addStartupItem(syntheticStartupMethod);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 7651bd5..3633506 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.experimental.startup.StartupOrder;
-import com.android.tools.r8.experimental.startup.profile.art.ARTProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
@@ -209,9 +208,7 @@
       DexApplication application, MainDexInfo mainDexInfo) {
     ClassToFeatureSplitMap classToFeatureSplitMap =
         ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(application.options);
-    StartupOrder startupOrder =
-        StartupOrder.createInitialStartupOrder(
-            application.options, SyntheticToSyntheticContextGeneralization.createForR8());
+    StartupOrder startupOrder = StartupOrder.createInitialStartupOrderForR8(application);
     AppInfoWithClassHierarchy appInfo =
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
             application,
diff --git a/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.java b/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.java
new file mode 100644
index 0000000..716480d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnostic.java
@@ -0,0 +1,121 @@
+// 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.startup.diagnostic;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.experimental.startup.profile.StartupClass;
+import com.android.tools.r8.experimental.startup.profile.StartupMethod;
+import com.android.tools.r8.experimental.startup.profile.SyntheticStartupMethod;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+@Keep
+public class MissingStartupProfileItemsDiagnostic implements Diagnostic {
+
+  private final List<DexReference> missingStartupItems;
+  private final Origin origin;
+
+  MissingStartupProfileItemsDiagnostic(List<DexReference> missingStartupItems, Origin origin) {
+    assert !missingStartupItems.isEmpty();
+    this.missingStartupItems = missingStartupItems;
+    this.origin = origin;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder();
+    Iterator<DexReference> missingStartupItemIterator = missingStartupItems.iterator();
+
+    // Write first missing startup item.
+    writeMissingStartupItem(builder, missingStartupItemIterator.next());
+
+    // Write remaining missing startup items with line separator before.
+    while (missingStartupItemIterator.hasNext()) {
+      writeMissingStartupItem(
+          builder.append(System.lineSeparator()), missingStartupItemIterator.next());
+    }
+
+    return builder.toString();
+  }
+
+  private void writeMissingStartupItem(StringBuilder builder, DexReference missingStartupItem) {
+    missingStartupItem.apply(
+        missingStartupClass -> builder.append("Startup class not found: "),
+        missingStartupField -> builder.append("Startup field not found: "),
+        missingStartupMethod -> builder.append("Startup method not found: "));
+    builder.append(missingStartupItem.toSourceString());
+  }
+
+  public static class Builder {
+
+    private final DexDefinitionSupplier definitions;
+    private final Set<DexReference> missingStartupItems = Sets.newIdentityHashSet();
+
+    private Origin origin;
+
+    public Builder(DexDefinitionSupplier definitions) {
+      this.definitions = definitions;
+    }
+
+    public boolean hasMissingStartupItems() {
+      return !missingStartupItems.isEmpty();
+    }
+
+    public boolean registerStartupClass(StartupClass startupClass) {
+      if (definitions != null && definitions.definitionFor(startupClass.getReference()) == null) {
+        missingStartupItems.add(startupClass.getReference());
+        return true;
+      }
+      return false;
+    }
+
+    public boolean registerStartupMethod(StartupMethod startupMethod) {
+      if (definitions != null && definitions.definitionFor(startupMethod.getReference()) == null) {
+        missingStartupItems.add(startupMethod.getReference());
+        return true;
+      }
+      return false;
+    }
+
+    public boolean registerSyntheticStartupMethod(SyntheticStartupMethod syntheticStartupMethod) {
+      if (definitions != null
+          && definitions.definitionFor(syntheticStartupMethod.getSyntheticContextType()) == null) {
+        missingStartupItems.add(syntheticStartupMethod.getSyntheticContextType());
+        return true;
+      }
+      return false;
+    }
+
+    public Builder setOrigin(Origin origin) {
+      this.origin = origin;
+      return this;
+    }
+
+    public MissingStartupProfileItemsDiagnostic build() {
+      assert hasMissingStartupItems();
+      List<DexReference> sortedMissingStartupItems = new ArrayList<>(missingStartupItems);
+      sortedMissingStartupItems.sort(DexReference::compareTo);
+      return new MissingStartupProfileItemsDiagnostic(sortedMissingStartupItems, origin);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java b/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java
new file mode 100644
index 0000000..f687df4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java
@@ -0,0 +1,118 @@
+// 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.startup.diagnostic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.StartupProfileBuilder;
+import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MissingStartupProfileItemsDiagnosticTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(Backend.DEX)
+        .addProgramClasses(Main.class)
+        .addOptionsModification(
+            options ->
+                options
+                    .getStartupOptions()
+                    .setStartupProfileProviders(getStartupProfileProviders()))
+        .release()
+        .setIntermediate(true)
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compileWithExpectedDiagnostics(this::inspectDiagnostics);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options
+                    .getStartupOptions()
+                    .setStartupProfileProviders(getStartupProfileProviders()))
+        .allowDiagnosticWarningMessages()
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compileWithExpectedDiagnostics(this::inspectDiagnostics);
+  }
+
+  private static Collection<StartupProfileProvider> getStartupProfileProviders() {
+    StartupProfileProvider startupProfileProvider =
+        new StartupProfileProvider() {
+          @Override
+          public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
+            ClassReference fooClassReference = Reference.classFromTypeName("Foo");
+            ClassReference barClassReference = Reference.classFromTypeName("Bar");
+            ClassReference bazClassReference = Reference.classFromTypeName("Baz");
+            startupProfileBuilder
+                .addStartupClass(
+                    startupClassBuilder -> startupClassBuilder.setClassReference(fooClassReference))
+                .addStartupMethod(
+                    startupMethodBuilder ->
+                        startupMethodBuilder.setMethodReference(
+                            MethodReferenceUtils.mainMethod(barClassReference)))
+                .addSyntheticStartupMethod(
+                    syntheticStartupMethodBuilder ->
+                        syntheticStartupMethodBuilder.setSyntheticContextReference(
+                            bazClassReference));
+          }
+
+          @Override
+          public Origin getOrigin() {
+            return Origin.unknown();
+          }
+        };
+    return Collections.singleton(startupProfileProvider);
+  }
+
+  private void inspectDiagnostics(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertWarningsMatch(
+        allOf(
+            diagnosticType(MissingStartupProfileItemsDiagnostic.class),
+            diagnosticMessage(
+                equalTo(
+                    StringUtils.joinLines(
+                        "Startup method not found: void Bar.main(java.lang.String[])",
+                        "Startup class not found: Baz",
+                        "Startup class not found: Foo")))));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}