Log synthetic context from synthetics in startup instrumentation

Prior to this CL, when a synthetic method was executed, the instrumentation would print the reference of the synthetic method, where the holder is replaced by the synthetic context. This gives more knowledge of which synthetic methods are executed.

Example trace event: "SLcom/example/SyntheticContext;->syntheticMethod()V"

For simplicity, this changes the instrumentation of synthetic methods to only print the synthetic context. Follow up work is then needed to improve the precision for synthetics.

New trace event: "SLcom/example/SyntheticContext;"

The interpretation of the new trace event is that any method of any synthetic from com.example.SyntheticContext has been executed.

Change-Id: Ic76ef7b879a582e5f2d6b5555e4d51da690b6391
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
index 0f76036..7fdb3ad 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
 
 // TODO(b/238173796): When updating the compiler to have support for taking a list of startup
 //  methods, this class may likely be removed along with the StartupItem class, so that only
@@ -25,6 +27,10 @@
     return new Builder<>();
   }
 
+  public static Builder<ClassReference, MethodReference> referenceBuilder() {
+    return new Builder<>();
+  }
+
   @Override
   public boolean isStartupClass() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
index 0678602..76abc9d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.experimental.startup;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.utils.PredicateUtils.not;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 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.DexValue.DexValueBoolean;
 import com.android.tools.r8.graph.DexValue.DexValueString;
@@ -32,9 +34,12 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.IRToDexFinalizer;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.startup.generated.InstrumentationServerFactory;
 import com.android.tools.r8.startup.generated.InstrumentationServerImplFactory;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -48,15 +53,17 @@
   private final AppView<AppInfo> appView;
   private final IRConverter converter;
   private final DexItemFactory dexItemFactory;
-  private final StartupOptions options;
+  private final InternalOptions options;
   private final StartupReferences references;
+  private final StartupOptions startupOptions;
 
   private StartupInstrumentation(AppView<AppInfo> appView) {
     this.appView = appView;
     this.converter = new IRConverter(appView, Timing.empty());
     this.dexItemFactory = appView.dexItemFactory();
-    this.options = appView.options().getStartupOptions();
+    this.options = appView.options();
     this.references = new StartupReferences(dexItemFactory);
+    this.startupOptions = options.getStartupOptions();
   }
 
   public static void run(AppView<AppInfo> appView, ExecutorService executorService)
@@ -74,6 +81,24 @@
 
   private void injectStartupRuntimeLibrary(ExecutorService executorService)
       throws ExecutionException {
+    // Only inject the startup instrumentation server if it is not already in the app.
+    if (appView.definitionFor(references.instrumentationServerImplType) != null) {
+      return;
+    }
+
+    // If the startup options has a synthetic context for the startup instrumentation server, then
+    // only inject the runtime library if the synthetic context exists in program to avoid injecting
+    // the runtime library multiple times when there is separate compilation.
+    if (startupOptions.hasStartupInstrumentationServerSyntheticContext()) {
+      DexType syntheticContext =
+          dexItemFactory.createType(
+              DescriptorUtils.javaTypeToDescriptor(
+                  startupOptions.getStartupInstrumentationServerSyntheticContext()));
+      if (asProgramClassOrNull(appView.definitionFor(syntheticContext)) == null) {
+        return;
+      }
+    }
+
     List<DexProgramClass> extraProgramClasses = createStartupRuntimeLibraryClasses();
     converter.processClassesConcurrently(extraProgramClasses, executorService);
 
@@ -88,7 +113,7 @@
   private List<DexProgramClass> createStartupRuntimeLibraryClasses() {
     DexProgramClass instrumentationServerImplClass =
         InstrumentationServerImplFactory.createClass(dexItemFactory);
-    if (options.hasStartupInstrumentationTag()) {
+    if (startupOptions.hasStartupInstrumentationTag()) {
       instrumentationServerImplClass
           .lookupUniqueStaticFieldWithName(dexItemFactory.createString("writeToLogcat"))
           .setStaticValue(DexValueBoolean.create(true));
@@ -96,7 +121,7 @@
           .lookupUniqueStaticFieldWithName(dexItemFactory.createString("logcatTag"))
           .setStaticValue(
               new DexValueString(
-                  dexItemFactory.createString(options.getStartupInstrumentationTag())));
+                  dexItemFactory.createString(startupOptions.getStartupInstrumentationTag())));
     }
 
     return ImmutableList.of(
@@ -104,57 +129,93 @@
   }
 
   private void instrumentClass(DexProgramClass clazz) {
-    ensureClassInitializer(clazz);
-    clazz.forEachProgramMethod(this::instrumentMethod);
-  }
-
-  private void ensureClassInitializer(DexProgramClass clazz) {
-    if (!clazz.hasClassInitializer()) {
-      ComputedApiLevel computedApiLevel =
-          appView.apiLevelCompute().computeInitialMinApiLevel(appView.options());
-      DexReturnVoid returnInstruction = new DexReturnVoid();
-      returnInstruction.setOffset(0);
-      clazz.addDirectMethod(
-          DexEncodedMethod.syntheticBuilder()
-              .setAccessFlags(MethodAccessFlags.createForClassInitializer())
-              .setApiLevelForCode(computedApiLevel)
-              .setApiLevelForDefinition(computedApiLevel)
-              .setClassFileVersion(CfVersion.V1_6)
-              .setCode(new DexCode(0, 0, 0, new DexInstruction[] {returnInstruction}))
-              .setMethod(dexItemFactory.createClassInitializer(clazz.getType()))
-              .build());
-    }
-  }
-
-  private void instrumentMethod(ProgramMethod method) {
-    DexMethod methodToInvoke;
-    DexMethod methodToPrint;
-    SyntheticItems syntheticItems = appView.getSyntheticItems();
-    if (syntheticItems.isSyntheticClass(method.getHolder())) {
-      Collection<DexType> synthesizingContexts =
-          syntheticItems.getSynthesizingContextTypes(method.getHolderType());
-      assert synthesizingContexts.size() == 1;
-      DexType synthesizingContext = synthesizingContexts.iterator().next();
-      methodToInvoke = references.addSyntheticMethod;
-      methodToPrint = method.getReference().withHolder(synthesizingContext, dexItemFactory);
-    } else {
-      methodToInvoke = references.addNonSyntheticMethod;
-      methodToPrint = method.getReference();
+    // Do not instrument the instrumentation server if it is already in the app.
+    if (clazz.getType() == references.instrumentationServerType
+        || clazz.getType() == references.instrumentationServerImplType) {
+      return;
     }
 
-    IRCode code = method.buildIR(appView);
+    boolean addedClassInitializer = ensureClassInitializer(clazz);
+    clazz.forEachProgramMethodMatching(
+        DexEncodedMethod::hasCode,
+        method ->
+            instrumentMethod(
+                method, method.getDefinition().isClassInitializer() && addedClassInitializer));
+  }
+
+  private boolean ensureClassInitializer(DexProgramClass clazz) {
+    if (clazz.hasClassInitializer()) {
+      return false;
+    }
+    ComputedApiLevel computedApiLevel =
+        appView.apiLevelCompute().computeInitialMinApiLevel(options);
+    DexReturnVoid returnInstruction = new DexReturnVoid();
+    returnInstruction.setOffset(0);
+    clazz.addDirectMethod(
+        DexEncodedMethod.syntheticBuilder()
+            .setAccessFlags(MethodAccessFlags.createForClassInitializer())
+            .setApiLevelForCode(computedApiLevel)
+            .setApiLevelForDefinition(computedApiLevel)
+            .setClassFileVersion(CfVersion.V1_6)
+            .setCode(new DexCode(0, 0, 0, new DexInstruction[] {returnInstruction}))
+            .setMethod(dexItemFactory.createClassInitializer(clazz.getType()))
+            .build());
+    return true;
+  }
+
+  private void instrumentMethod(ProgramMethod method, boolean skipMethodLogging) {
+    // Disable StringSwitch conversion to avoid having to run the StringSwitchRemover before
+    // finalizing the code.
+    MutableMethodConversionOptions conversionOptions =
+        new MutableMethodConversionOptions(options).disableStringSwitchConversion();
+    IRCode code = method.buildIR(appView, conversionOptions);
     InstructionListIterator instructionIterator = code.entryBlock().listIterator(code);
     instructionIterator.positionBeforeNextInstructionThatMatches(not(Instruction::isArgument));
 
-    Value descriptorValue =
-        instructionIterator.insertConstStringInstruction(
-            appView, code, dexItemFactory.createString(methodToPrint.toSmaliString()));
-    instructionIterator.add(
-        InvokeStatic.builder()
-            .setMethod(methodToInvoke)
-            .setSingleArgument(descriptorValue)
-            .setPosition(Position.syntheticNone())
-            .build());
+    // Insert invoke to record that the enclosing class is a startup class.
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    boolean isSyntheticClass = syntheticItems.isSyntheticClass(method.getHolder());
+    if (method.getDefinition().isClassInitializer() && !isSyntheticClass) {
+      DexMethod methodToInvoke = references.addNonSyntheticMethod;
+      DexType classToPrint = method.getHolderType();
+      Value descriptorValue =
+          instructionIterator.insertConstStringInstruction(
+              appView, code, dexItemFactory.createString(classToPrint.toSmaliString()));
+      instructionIterator.add(
+          InvokeStatic.builder()
+              .setMethod(methodToInvoke)
+              .setSingleArgument(descriptorValue)
+              .setPosition(Position.syntheticNone())
+              .build());
+    }
+
+    // Insert invoke to record the execution of the current method.
+    if (!skipMethodLogging) {
+      DexMethod methodToInvoke;
+      DexReference referenceToPrint;
+      if (isSyntheticClass) {
+        Collection<DexType> synthesizingContexts =
+            syntheticItems.getSynthesizingContextTypes(method.getHolderType());
+        assert synthesizingContexts.size() == 1;
+        DexType synthesizingContext = synthesizingContexts.iterator().next();
+        methodToInvoke = references.addSyntheticMethod;
+        referenceToPrint = synthesizingContext;
+      } else {
+        methodToInvoke = references.addNonSyntheticMethod;
+        referenceToPrint = method.getReference();
+      }
+
+      Value descriptorValue =
+          instructionIterator.insertConstStringInstruction(
+              appView, code, dexItemFactory.createString(referenceToPrint.toSmaliString()));
+      instructionIterator.add(
+          InvokeStatic.builder()
+              .setMethod(methodToInvoke)
+              .setSingleArgument(descriptorValue)
+              .setPosition(Position.syntheticNone())
+              .build());
+    }
+
     DexCode instrumentedCode =
         new IRToDexFinalizer(appView, converter.deadCodeRemover)
             .finalizeCode(code, BytecodeMetadataProvider.empty(), Timing.empty());
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
index 15fbf6c..7ff913d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
@@ -9,19 +9,76 @@
 
 public class StartupOptions {
 
+  /**
+   * When enabled, all startup classes will be placed in the primary classes.dex file. All other
+   * (non-startup) classes will be placed in classes2.dex, ..., classesN.dex.
+   */
   private boolean enableMinimalStartupDex =
       parseSystemPropertyForDevelopmentOrDefault(
           "com.android.tools.r8.startup.minimalstartupdex", false);
+
+  /**
+   * When enabled, each method that is not classified as a startup method at the end of compilation
+   * will be changed to have a throwing method body.
+   *
+   * <p>This is useful for testing if a given startup list is complete (and that R8 correctly
+   * rewrites the startup list in presence of optimizations).
+   */
   private boolean enableStartupCompletenessCheckForTesting =
       parseSystemPropertyForDevelopmentOrDefault(
           "com.android.tools.r8.startup.completenesscheck", false);
+
+  /**
+   * When enabled, each method will be instrumented to notify the startup InstrumentationServer that
+   * it has been executed.
+   *
+   * <p>This will also inject the startup runtime library (i.e., the InstrumentationServer) into the
+   * app.
+   */
   private boolean enableStartupInstrumentation =
       parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.startup.instrument", false);
+
+  /**
+   * Specifies the synthetic context of the startup runtime library. When this is set, the startup
+   * runtime library will only be injected into the app when the synthetic context is in the
+   * program. This can be used to avoid that the startup runtime library is injected multiple times
+   * in presence of separate compilation.
+   *
+   * <p>Example synthetic context: "app.tivi.home.MainActivity".
+   *
+   * <p>Note that this is only meaningful when {@link #enableStartupInstrumentation} is set to true.
+   */
+  private String startupInstrumentationServerSyntheticContext =
+      getSystemPropertyForDevelopment(
+          "com.android.tools.r8.startup.instrumentationserversyntheticcontext");
+
+  /**
+   * Specifies the logcat tag that should be used by the InstrumentationServer when logging events.
+   *
+   * <p>When a logcat tag is not specified, the InstrumentationServer will not print events to
+   * logcat. Instead, the startup events must be obtained by requesting the InstrumentationServer to
+   * write the events to a file.
+   */
   private String startupInstrumentationTag =
       getSystemPropertyForDevelopment("com.android.tools.r8.startup.instrumentationtag");
 
   private StartupConfiguration startupConfiguration;
 
+  public boolean hasStartupInstrumentationServerSyntheticContext() {
+    return startupInstrumentationServerSyntheticContext != null;
+  }
+
+  public String getStartupInstrumentationServerSyntheticContext() {
+    return startupInstrumentationServerSyntheticContext;
+  }
+
+  public StartupOptions setStartupInstrumentationServerSyntheticContext(
+      String startupInstrumentationServerSyntheticContext) {
+    this.startupInstrumentationServerSyntheticContext =
+        startupInstrumentationServerSyntheticContext;
+    return this;
+  }
+
   public boolean hasStartupInstrumentationTag() {
     return startupInstrumentationTag != null;
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupReferences.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupReferences.java
index 0c2de77..a041f1e 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupReferences.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupReferences.java
@@ -10,11 +10,14 @@
 
 public class StartupReferences {
 
+  final DexType instrumentationServerType;
   final DexType instrumentationServerImplType;
   final DexMethod addNonSyntheticMethod;
   final DexMethod addSyntheticMethod;
 
   StartupReferences(DexItemFactory dexItemFactory) {
+    instrumentationServerType =
+        dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServer;");
     instrumentationServerImplType =
         dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;");
     addNonSyntheticMethod =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
index 038a277..adf0102 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
@@ -35,8 +35,9 @@
       enablePeepholeOptimizations = false;
     }
 
-    public void disableStringSwitchConversion() {
+    public MutableMethodConversionOptions disableStringSwitchConversion() {
       enableStringSwitchConversion = false;
+      return this;
     }
 
     public MutableMethodConversionOptions setIsGeneratingClassFiles(
diff --git a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
index 6047b63..07f4487 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupItem;
 import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.references.ClassReference;
@@ -74,17 +75,17 @@
     return ImmutableList.of("foo");
   }
 
-  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
       throws NoSuchMethodException {
     return ImmutableList.of(
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(Main.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(AStartupClass.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(AStartupClass.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
index 5e25d36..7ae582f 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
@@ -13,6 +13,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupItem;
 import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -96,25 +97,25 @@
     return ImmutableList.of("A", "B", "C");
   }
 
-  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
       throws NoSuchMethodException {
-    ImmutableList.Builder<StartupMethod<ClassReference, MethodReference>> builder =
+    ImmutableList.Builder<StartupItem<ClassReference, MethodReference, ?>> builder =
         ImmutableList.builder();
     builder.add(
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(Main.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(A.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(A.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(A.class.getDeclaredMethod("a")))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(B.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(
@@ -122,21 +123,8 @@
             .build());
     if (useLambda) {
       builder.add(
-          StartupMethod.referenceBuilder()
-              .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
-              .setSynthetic()
-              .build(),
-          StartupMethod.referenceBuilder()
-              .setMethodReference(MethodReferenceUtils.instanceConstructor(B.class))
-              .setSynthetic()
-              .build(),
-          StartupMethod.referenceBuilder()
-              .setMethodReference(
-                  Reference.method(
-                      Reference.classFromClass(B.class),
-                      "accept",
-                      ImmutableList.of(Reference.classFromClass(Object.class)),
-                      null))
+          StartupClass.referenceBuilder()
+              .setClassReference(Reference.classFromClass(B.class))
               .setSynthetic()
               .build(),
           StartupMethod.referenceBuilder()
@@ -145,8 +133,8 @@
               .build());
     }
     builder.add(
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(C.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(C.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(C.class.getDeclaredMethod("c")))
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
index e1e3149..3087653 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
@@ -14,6 +14,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupItem;
 import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -30,7 +31,6 @@
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -93,46 +93,36 @@
     return ImmutableList.of("A", "B", "C");
   }
 
-  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
       throws NoSuchMethodException {
     return ImmutableList.of(
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(Main.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(A.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(A.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(A.class.getDeclaredMethod("a")))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(B.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(B.class.getDeclaredMethod("b")))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
-            .setSynthetic()
-            .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.instanceConstructor(B.class))
-            .setSynthetic()
-            .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(
-                Reference.method(
-                    Reference.classFromClass(B.class), "run", Collections.emptyList(), null))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(B.class))
             .setSynthetic()
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(B.class.getDeclaredMethod("lambda$b$0")))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(C.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(C.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(C.class.getDeclaredMethod("c")))