Hygienic outlining.

Bug: 158159959
Fixes: 177847090
Change-Id: Ieb0e43bc0da3f0076c69778467f68a3b89080078
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 56f9a92..c488caf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralMapping;
@@ -44,6 +43,7 @@
           "$r8$twr$utility",
           "$-DC",
           "$$ServiceLoaderMethods",
+          "com.android.tools.r8.GeneratedOutlineSupport",
           "-$$Lambda$");
 
   public final DexString descriptor;
@@ -338,7 +338,6 @@
     // Any entry that is removed from here must be added to OLD_SYNTHESIZED_NAMES to ensure that
     // newer releases can be used to merge previous builds.
     return name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
-        || name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
         || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME); // Global singleton.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 152d618..db1f69b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -101,7 +100,6 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -838,9 +836,8 @@
               outliner.identifyOutlineSites(code);
             },
             executorService);
-        DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
-        appView.appInfo().addSynthesizedClass(outlineClass, true);
-        optimizeSynthesizedClass(outlineClass, executorService);
+        List<ProgramMethod> outlineMethods = outliner.buildOutlineMethods();
+        optimizeSynthesizedMethods(outlineMethods, executorService);
         forEachSelectedOutliningMethod(
             methodsSelectedForOutlining,
             code -> {
@@ -852,8 +849,7 @@
             executorService);
         feedback.updateVisibleOptimizationInfo();
         assert outliner.checkAllOutlineSitesFoundAgain();
-        builder.addSynthesizedClass(outlineClass);
-        clearDexMethodCompilationState(outlineClass);
+        outlineMethods.forEach(m -> m.getDefinition().markNotProcessed());
       }
       timing.end();
     }
@@ -1037,27 +1033,11 @@
     }
   }
 
-  // Find an unused name for the outlining class. When multiple runs produces additional
-  // outlining the default outlining class might already be present.
-  private DexType computeOutlineClassType() {
-    DexType result;
-    int count = 0;
-    String prefix = appView.options().synthesizedClassPrefix.replace('/', '.');
-    do {
-      String name =
-          prefix + OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count));
-      count++;
-      result = appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(name));
-
-    } while (appView.appInfo().definitionForWithoutExistenceAssert(result) != null);
-    return result;
-  }
-
-  public void optimizeSynthesizedClass(
-      DexProgramClass clazz, ExecutorService executorService)
+  public void optimizeSynthesizedMethods(
+      List<ProgramMethod> programMethods, ExecutorService executorService)
       throws ExecutionException {
     // Process the generated class, but don't apply any outlining.
-    SortedProgramMethodSet methods = SortedProgramMethodSet.create(clazz::forEachProgramMethod);
+    SortedProgramMethodSet methods = SortedProgramMethodSet.create(programMethods::forEach);
     processMethodsConcurrently(methods, executorService);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 994e2ba..09fd687 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -165,11 +165,9 @@
   public void processSynthesizedClasses(IRConverter converter, ExecutorService executor)
       throws ExecutionException {
     while (!synthesizedMethods.isEmpty()) {
-      List<ProgramMethod> methods = new ArrayList<>(synthesizedMethods);
+      ArrayList<ProgramMethod> methods = new ArrayList<>(synthesizedMethods);
       synthesizedMethods.clear();
-      for (ProgramMethod method : methods) {
-        converter.optimizeSynthesizedClass(method.getHolder(), executor);
-      }
+      converter.optimizeSynthesizedMethods(methods, executor);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 11b4ddf..dbad5bd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -10,26 +10,16 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 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.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
@@ -65,8 +55,8 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -76,7 +66,6 @@
 import com.android.tools.r8.utils.collections.ProgramMethodMultiset;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -102,9 +91,9 @@
  *       {@link OutlineOptions#threshold}). Each selected method is then converted back to IR and
  *       passed to {@link Outliner#identifyOutlineSites(IRCode)}, which then stores concrete
  *       outlining candidates in {@link Outliner#outlineSites}.
- *   <li>Third, {@link Outliner#buildOutlinerClass(DexType)} is called to construct the <em>outline
- *       support class</em> containing a static helper method for each outline candidate that occurs
- *       frequently enough. Each selected method is then converted to IR, passed to {@link
+ *   <li>Third, {@link Outliner#buildOutlineMethods()} is called to construct the <em>outline
+ *       support classes</em> containing a static helper method for each outline candidate that
+ *       occurs frequently enough. Each selected method is then converted to IR, passed to {@link
  *       Outliner#applyOutliningCandidate(IRCode)} to perform the outlining, and converted back to
  *       the output format (DEX or CF).
  * </ul>
@@ -116,7 +105,7 @@
       new ArrayList<>();
   /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
   private final Map<Outline, List<ProgramMethod>> outlineSites = new HashMap<>();
-  /** Result of third step (see {@link Outliner#buildOutlinerClass(DexType)}. */
+  /** Result of third step (see {@link Outliner#buildOutlineMethods()}. */
   private final Map<Outline, DexMethod> generatedOutlines = new HashMap<>();
 
   static final int MAX_IN_SIZE = 5;  // Avoid using ranged calls for outlined code.
@@ -581,11 +570,6 @@
       return proto;
     }
 
-    // Build the DexMethod for this outline.
-    DexMethod buildMethod(DexType clazz, DexString name) {
-      return appView.dexItemFactory().createMethod(clazz, buildProto(), name);
-    }
-
     @Override
     public boolean equals(Object other) {
       if (!(other instanceof Outline)) {
@@ -1355,67 +1339,40 @@
     return methodsSelectedForOutlining;
   }
 
-  public DexProgramClass buildOutlinerClass(DexType type) {
-    // Build the outlined methods.
-    // By now the candidates are the actual selected outlines. Name the generated methods in a
-    // consistent order, to provide deterministic output.
+  public List<ProgramMethod> buildOutlineMethods() {
+    List<ProgramMethod> outlineMethods = new ArrayList<>();
+    // By now the candidates are the actual selected outlines. Iterate the outlines in a
+    // consistent order, to provide deterministic naming of the internal-synthetics.
+    // The choice of 'representative' will ensure deterministic naming of the external names.
     List<Outline> outlines = selectOutlines();
     outlines.sort(Comparator.naturalOrder());
-    DexEncodedMethod[] direct = new DexEncodedMethod[outlines.size()];
-    int count = 0;
     for (Outline outline : outlines) {
-      MethodAccessFlags methodAccess =
-          MethodAccessFlags.fromSharedAccessFlags(
-              Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
-      DexString methodName =
-          appView.dexItemFactory().createString(OutlineOptions.METHOD_PREFIX + count);
-      DexMethod method = outline.buildMethod(type, methodName);
       List<ProgramMethod> sites = outlineSites.get(outline);
       assert !sites.isEmpty();
-      direct[count] =
-          new DexEncodedMethod(
-              method,
-              methodAccess,
-              MethodTypeSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              new OutlineCode(outline),
-              true);
-      if (appView.options().isGeneratingClassFiles()) {
-        direct[count].upgradeClassFileVersion(sites.get(0).getDefinition().getClassFileVersion());
-      }
-      generatedOutlines.put(outline, method);
-      count++;
+      ProgramMethod representative = findDeterministicRepresentative(sites);
+      ProgramMethod outlineMethod =
+          appView
+              .getSyntheticItems()
+              .createMethod(
+                  SyntheticKind.OUTLINE,
+                  representative,
+                  appView.dexItemFactory(),
+                  builder -> {
+                    builder
+                        .setAccessFlags(
+                            MethodAccessFlags.fromSharedAccessFlags(
+                                Constants.ACC_PUBLIC | Constants.ACC_STATIC, false))
+                        .setProto(outline.buildProto())
+                        .setCode(m -> new OutlineCode(outline));
+                    if (appView.options().isGeneratingClassFiles()) {
+                      builder.setClassFileVersion(
+                          representative.getDefinition().getClassFileVersion());
+                    }
+                  });
+      generatedOutlines.put(outline, outlineMethod.getReference());
+      outlineMethods.add(outlineMethod);
     }
-    // No need to sort the direct methods as they are generated in sorted order.
-
-    // Build the outliner class.
-    DexTypeList interfaces = DexTypeList.empty();
-    DexString sourceFile = appView.dexItemFactory().createString("outline");
-    ClassAccessFlags accessFlags = ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC);
-    assert !appView.options().encodeChecksums;
-    // The outliner is R8 only and checksum is not a supported part of R8 compilation.
-    ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
-    return new DexProgramClass(
-        type,
-        null,
-        new SynthesizedOrigin("outlining", getClass()),
-        accessFlags,
-        appView.dexItemFactory().objectType,
-        interfaces,
-        sourceFile,
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        ClassSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY, // Static fields.
-        DexEncodedField.EMPTY_ARRAY, // Instance fields.
-        direct,
-        DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
-        appView.dexItemFactory().getSkipNameValidationForTesting(),
-        checksumSupplier);
+    return outlineMethods;
   }
 
   private List<Outline> selectOutlines() {
@@ -1430,6 +1387,18 @@
     return result;
   }
 
+  private static ProgramMethod findDeterministicRepresentative(List<ProgramMethod> members) {
+    // Pick a deterministic member as representative.
+    ProgramMethod smallest = members.get(0);
+    for (int i = 1; i < members.size(); i++) {
+      ProgramMethod next = members.get(i);
+      if (next.getReference().compareTo(smallest.getReference()) < 0) {
+        smallest = next;
+      }
+    }
+    return smallest;
+  }
+
   public void applyOutliningCandidate(IRCode code) {
     assert !code.method().getCode().isOutlineCode();
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f7ac800..a9d8d57 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -109,7 +109,6 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -1724,8 +1723,6 @@
             || !options.testing.checkForNotExpandingMainDexTracingResult
             || previousMainDexTracingResult.isRoot(clazz)
             || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
-            // TODO(b/177847090): Consider not outlining anything in main dex.
-            || clazz.toSourceString().contains(OutlineOptions.CLASS_NAME)
         : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
 
     // Mark types in inner-class attributes referenced.
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index b33e260..fc5b278 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -32,7 +32,8 @@
     THROW_ICCE("ThrowICCE", true),
     THROW_NSME("ThrowNSME", true),
     TWR_CLOSE_RESOURCE("TwrCloseResource", true),
-    SERVICE_LOADER("ServiceLoad", true);
+    SERVICE_LOADER("ServiceLoad", true),
+    OUTLINE("Outline", true);
 
     public final String descriptor;
     public final boolean isSingleSyntheticMethod;
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 a8bec4a..a9f55ee 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1067,10 +1067,6 @@
   }
 
   public static class OutlineOptions {
-
-    public static final String CLASS_NAME = "com.android.tools.r8.GeneratedOutlineSupport";
-    public static final String METHOD_PREFIX = "outline";
-
     public boolean enabled = true;
     public int minSize = 3;
     public int maxSize = 99;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java
index 4af2593..2a837bc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -72,8 +72,9 @@
     assertThat(classSubject.uniqueMethodWithName("foo"), isPresent());
     assertThat(classSubject.uniqueMethodWithName("bar"), isPresent());
     boolean hasOutlineClass =
-        inspector.allClasses().stream()
-            .anyMatch(c -> c.getFinalName().equals(OutlineOptions.CLASS_NAME));
+        inspector
+            .clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0))
+            .isPresent();
     assertEquals(forceOutline || parameters.isDexRuntime(), hasOutlineClass);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
index 59602da..6b7bc4a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -93,9 +93,10 @@
   }
 
   private void validateOutlining(CodeInspector inspector, Class<?> main) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
-    assertThat(outlineClass, isPresent());
-    MethodSubject outlineMethod = outlineClass.uniqueMethodWithName("outline0");
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(main, 0));
+    MethodSubject outlineMethod =
+        outlineClass.uniqueMethodWithName(SyntheticItemsTestUtils.syntheticMethodName());
     assertThat(outlineMethod, isPresent());
 
     ClassSubject argClass = inspector.clazz(TestArg.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
index 099afa8..440b036 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -37,11 +37,12 @@
   }
 
   private void validateOutlining(CodeInspector inspector) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     MethodSubject outline0Method =
         outlineClass.method(
             "void",
-            "outline0",
+            SyntheticItemsTestUtils.syntheticMethodName(),
             ImmutableList.of(
                 TestClass.class.getTypeName() + "[]", TestClass.class.getTypeName() + "[]"));
     assertThat(outline0Method, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
index fb4417f9..062516c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
@@ -11,8 +11,8 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -44,12 +44,15 @@
   private void validateOutlining(CodeInspector inspector) {
     // No outlining when arrays of interfaces are involved, see b/132420510 - unless the testing
     // option is set.
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     if (allowOutlinerInterfaceArrayArguments && parameters.isCfRuntime()) {
       assertThat(outlineClass, isPresent());
       MethodSubject outline0Method =
           outlineClass.method(
-              "void", "outline0", ImmutableList.of("java.lang.Object[]", "java.lang.Object[]"));
+              "void",
+              SyntheticItemsTestUtils.syntheticMethodName(),
+              ImmutableList.of("java.lang.Object[]", "java.lang.Object[]"));
       assertThat(outline0Method, isPresent());
       ClassSubject classSubject = inspector.clazz(TestClass.class);
       assertThat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
index 7a69926..36d9ad5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -37,9 +37,13 @@
   }
 
   private void validateOutlining(CodeInspector inspector) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     MethodSubject outline0Method =
-        outlineClass.method("void", "outline0", ImmutableList.of("int[]", "int[]"));
+        outlineClass.method(
+            "void",
+            SyntheticItemsTestUtils.syntheticMethodName(),
+            ImmutableList.of("int[]", "int[]"));
     assertThat(outline0Method, isPresent());
     ClassSubject classSubject = inspector.clazz(TestClass.class);
     assertThat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
index 9fcc6b7..b4dd6b7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -98,11 +99,14 @@
         .inspector();
 
     for (FoundClassSubject clazz : inspector.allClasses()) {
-      clazz.forAllMethods(method -> {
-        if (method.hasCode() && !method.getFinalName().startsWith("outline")) {
-          verifyAbsenceOfStringBuilderAppend(method.streamInstructions());
-        }
-      });
+      if (!SyntheticItemsTestUtils.isExternalOutlineClass(clazz.getFinalReference())) {
+        clazz.forAllMethods(
+            method -> {
+              if (method.hasCode()) {
+                verifyAbsenceOfStringBuilderAppend(method.streamInstructions());
+              }
+            });
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
index 66c42e8..031fd9d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ir.optimize.outliner.b133215941.B133215941.TestClass.ClassWithStaticMethod;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -41,11 +41,12 @@
   }
 
   private void validateOutlining(CodeInspector inspector) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     MethodSubject outline0Method =
         outlineClass.method(
             "void",
-            "outline0",
+            SyntheticItemsTestUtils.syntheticMethodName(),
             ImmutableList.of(TestClass.class.getTypeName(), TestClass.class.getTypeName()));
     assertThat(outline0Method, isPresent());
     ClassSubject classSubject = inspector.clazz(TestClass.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
index 438aa81..dd6dace 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.outliner.b149971007;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertFalse;
@@ -15,7 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.dexsplitter.SplitterTestBase;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -24,6 +25,8 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -64,10 +67,17 @@
   }
 
   private void checkOutlineFromFeature(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
-    assertThat(clazz, isPresent());
-    assertEquals(2, clazz.allMethods().size());
-    assertTrue(clazz.allMethods().stream().anyMatch(this::referenceFeatureClass));
+    // There are two expected outlines, each is currently in its own class.
+    List<FoundMethodSubject> allMethods = new ArrayList<>();
+    for (int i = 0; i < 2; i++) {
+      ClassSubject clazz =
+          inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, i));
+      assertThat(clazz, isPresent());
+      assertEquals(1, clazz.allMethods().size());
+      allMethods.addAll(clazz.allMethods());
+    }
+    // One of the methods is StringBuilder the other references the feature.
+    assertTrue(allMethods.stream().anyMatch(this::referenceFeatureClass));
   }
 
   @Test
@@ -85,24 +95,44 @@
     // Check that parts of method1, ..., method4 in FeatureClass was outlined.
     ClassSubject featureClass = compileResult.inspector().clazz(FeatureClass.class);
     assertThat(featureClass, isPresent());
-    String outlineClassName =
+
+    // Find the final names of the two outline classes.
+    String outlineClassName0 =
         ClassNameMapper.mapperFromString(compileResult.getProguardMap())
             .getObfuscatedToOriginalMapping()
             .inverse
-            .get(OutlineOptions.CLASS_NAME);
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName));
+            .get(
+                SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 0).getTypeName());
+    String outlineClassName1 =
+        ClassNameMapper.mapperFromString(compileResult.getProguardMap())
+            .getObfuscatedToOriginalMapping()
+            .inverse
+            .get(
+                SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 1).getTypeName());
+
+    // Verify they are called from the feature methods.
+    // Note: should the choice of synthetic grouping change these expectations will too.
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName0));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName0));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName1));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName1));
 
     compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("123456");
   }
 
   private void checkNoOutlineFromFeature(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
-    assertThat(clazz, isPresent());
-    assertEquals(1, clazz.allMethods().size());
-    assertTrue(clazz.allMethods().stream().noneMatch(this::referenceFeatureClass));
+    // The features do not give rise to an outline.
+    ClassSubject featureOutlineClass =
+        inspector.clazz(
+            SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 0).getTypeName());
+    assertThat(featureOutlineClass, isAbsent());
+    // The main TestClass entry does.
+    ClassSubject mainOutlineClazz =
+        inspector.clazz(
+            SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0).getTypeName());
+    assertThat(mainOutlineClazz, isPresent());
+    assertEquals(1, mainOutlineClazz.allMethods().size());
+    assertTrue(mainOutlineClazz.allMethods().stream().noneMatch(this::referenceFeatureClass));
   }
 
   @Test
@@ -124,12 +154,16 @@
     // Check that parts of method1, ..., method4 in FeatureClass was not outlined.
     CodeInspector featureInspector = new CodeInspector(featureCode);
     ClassSubject featureClass = featureInspector.clazz(FeatureClass.class);
+
+    // Note, this code does not really check a valid property now as the name of the outline is not
+    // known.
     assertThat(featureClass, isPresent());
     String outlineClassName =
         ClassNameMapper.mapperFromString(compileResult.getProguardMap())
             .getObfuscatedToOriginalMapping()
             .inverse
-            .get(OutlineOptions.CLASS_NAME);
+            .get(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0).getTypeName());
+
     assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
     assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
     assertFalse(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
index eab6811..39c95f1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -37,11 +37,12 @@
   }
 
   private void validateOutlining(CodeInspector inspector) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0));
     MethodSubject outline0Method =
         outlineClass.method(
             "void",
-            "outline0",
+            SyntheticItemsTestUtils.syntheticMethodName(),
             ImmutableList.of(
                 StringBuilder.class.getTypeName(),
                 String.class.getTypeName(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
index db3ef77..4b81714 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -38,10 +38,13 @@
   }
 
   private void validateOutlining(CodeInspector inspector, Class<?> testClass, String argumentType) {
-    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    ClassSubject outlineClass =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(testClass, 0));
     MethodSubject outline0Method =
         outlineClass.method(
-            "java.lang.String", "outline0", ImmutableList.of(argumentType, argumentType));
+            "java.lang.String",
+            SyntheticItemsTestUtils.syntheticMethodName(),
+            ImmutableList.of(argumentType, argumentType));
     assertThat(outline0Method, isPresent());
     ClassSubject classSubject = inspector.clazz(testClass);
     assertThat(
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 04ac085..efe3826 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -30,14 +30,18 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+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.Reference;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -50,6 +54,10 @@
 
 public class OutlineTest extends SmaliTestBase {
 
+  private static ClassReference OUTLINE_CLASS =
+      SyntheticItemsTestUtils.syntheticOutlineClass(
+          Reference.classFromTypeName(DEFAULT_CLASS_NAME), 0);
+
   private static final String stringBuilderAppendSignature =
       "Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;";
   private static final String stringBuilderAppendDoubleSignature =
@@ -85,12 +93,13 @@
   }
 
   private String firstOutlineMethodName() {
-    return OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX + "0";
+    return OUTLINE_CLASS.getTypeName() + '.' + SyntheticItemsTestUtils.syntheticMethodName();
   }
 
-  private boolean isOutlineMethodName(String qualifiedName) {
-    String qualifiedPrefix = OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX;
-    return qualifiedName.indexOf(qualifiedPrefix) == 0;
+  private static boolean isOutlineMethodName(DexMethod method) {
+    return SyntheticItemsTestUtils.isExternalOutlineClass(
+            Reference.classFromDescriptor(method.holder.toDescriptorString()))
+        && method.name.toString().equals(SyntheticItemsTestUtils.syntheticMethodName());
   }
 
   @Test
@@ -142,7 +151,6 @@
 
       AndroidApp originalApplication = buildApplication(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -151,7 +159,7 @@
       assertTrue(code.instructions[0] instanceof ConstString);
       assertTrue(code.instructions[1] instanceof InvokeStatic);
       InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod()));
 
       // Run code and check result.
       String result = runArt(processedApplication);
@@ -211,7 +219,6 @@
 
       AndroidApp originalApplication = buildApplication(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -225,7 +232,7 @@
       }
       assertTrue(code.instructions[firstOutlineInvoke] instanceof InvokeStatic);
       InvokeStatic invoke = (InvokeStatic) code.instructions[firstOutlineInvoke];
-      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod()));
 
       // Run code and check result.
       String result = runArt(processedApplication);
@@ -273,7 +280,6 @@
     Consumer<InternalOptions> options = configureOutlineOptions(outline -> outline.threshold = 1);
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -282,7 +288,7 @@
     assertTrue(code.instructions[0] instanceof ConstString);
     assertTrue(code.instructions[1] instanceof InvokeStatic);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -336,7 +342,6 @@
     Consumer<InternalOptions> options = configureOutlineOptions(outline -> outline.threshold = 1);
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -346,7 +351,7 @@
     assertTrue(code.instructions[1] instanceof ConstString);
     assertTrue(code.instructions[2] instanceof InvokeStatic);
     InvokeStatic invoke = (InvokeStatic) code.instructions[2];
-    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -402,7 +407,6 @@
 
       AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -412,13 +416,13 @@
       if (i < 3) {
         assertTrue(code.instructions[1] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else {
         assertTrue(code.instructions[1] instanceof InvokeVirtual);
         assertTrue(code.instructions[2] instanceof InvokeVirtual);
         assertTrue(code.instructions[3] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[3];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       }
 
       // Run code and check result.
@@ -480,7 +484,6 @@
 
       AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -490,13 +493,13 @@
       if (i < 3) {
         assertTrue(code.instructions[1] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else {
         assertTrue(code.instructions[1] instanceof InvokeVirtual);
         assertTrue(code.instructions[2] instanceof InvokeVirtual);
         assertTrue(code.instructions[3] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[3];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       }
 
       // Run code and check result.
@@ -553,7 +556,6 @@
 
       AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed main method for inspection.
       DexEncodedMethod mainMethod = getMethod(processedApplication, mainSignature);
@@ -569,14 +571,14 @@
       }
       if (i == 2) {
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[4];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else if (i == 3) {
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[1];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else {
         assert i == 4 || i == 5;
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[2];
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       }
 
       // Run code and check result.
@@ -653,19 +655,19 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
+    assertEquals(3, getNumberOfProgramClasses(processedApplication));
 
     DexCode code1 = getMethod(processedApplication, signature1).getCode().asDexCode();
     assertEquals(4, code1.instructions.length);
     assertTrue(code1.instructions[1] instanceof InvokeStatic);
     InvokeStatic invoke1 = (InvokeStatic) code1.instructions[1];
-    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod()));
 
     DexCode code2 = getMethod(processedApplication, signature2).getCode().asDexCode();
     assertEquals(5, code2.instructions.length);
     assertTrue(code2.instructions[2] instanceof InvokeStatic);
     InvokeStatic invoke2 = (InvokeStatic) code2.instructions[2];
-    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -721,7 +723,6 @@
 
       AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
       AndroidApp processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       DexCode code = getMethod(processedApplication, signature).getCode().asDexCode();
       int outlineInstructionIndex;
@@ -739,10 +740,10 @@
       Instruction instruction = code.instructions[outlineInstructionIndex];
       if (instruction instanceof InvokeStatic) {
         InvokeStatic invoke = (InvokeStatic) instruction;
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       } else {
         InvokeStaticRange invoke = (InvokeStaticRange) instruction;
-        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod()));
       }
 
       // Run code and check result.
@@ -791,7 +792,7 @@
     InvokeStatic invoke;
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     invoke = (InvokeStatic) code.instructions[0];
-    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -859,16 +860,15 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
+    assertEquals(4, getNumberOfProgramClasses(processedApplication));
 
     // Check that three outlining methods was created.
     CodeInspector inspector = new CodeInspector(processedApplication);
-    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
-    assertTrue(clazz.isPresent());
-    assertEquals(3, clazz.getDexProgramClass().getMethodCollection().numberOfDirectMethods());
+    List<DexEncodedMethod> outlineMethods = getOutlineMethods(inspector);
+    assertEquals(3, outlineMethods.size());
     // Collect the return types of the outlines for the body of method1 and method2.
     List<DexType> r = new ArrayList<>();
-    for (DexEncodedMethod directMethod : clazz.getDexProgramClass().directMethods()) {
+    for (DexEncodedMethod directMethod : outlineMethods) {
       if (directMethod.getCode().asDexCode().instructions[0] instanceof InvokeVirtual) {
         r.add(directMethod.method.proto.returnType);
       }
@@ -885,6 +885,16 @@
     assertEquals("TestTestTestTestTest", result);
   }
 
+  private static List<DexEncodedMethod> getOutlineMethods(CodeInspector inspector) {
+    List<DexEncodedMethod> outlineMethods = new ArrayList<>();
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (SyntheticItemsTestUtils.isExternalOutlineClass(clazz.getFinalReference())) {
+        clazz.forAllMethods(m -> outlineMethods.add(m.getMethod()));
+      }
+    }
+    return outlineMethods;
+  }
+
   @Test
   public void outlineMultipleTimes() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
@@ -931,15 +941,18 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
+    assertEquals(3, getNumberOfProgramClasses(processedApplication));
 
     final int count = 10;
-    // Process the application several times. Each time will outline the previous outline.
-    for (int i = 0; i < count; i++) {
+    // Process the application several times. Each time will outline the previous outlines.
+    for (int i = 1; i < count; i++) {
       // Build a new application with the Outliner class.
       originalApplication = processedApplication;
-      processedApplication = processApplication(originalApplication, options);
-      assertEquals(i + 3, getNumberOfProgramClasses(processedApplication));
+      processedApplication =
+          processApplication(
+              originalApplication,
+              options.andThen(o -> o.testing.allowConflictingSyntheticTypes = true));
+      assertEquals((i + 1) * 3, getNumberOfProgramClasses(processedApplication));
     }
 
     // Process the application several times. No more outlining as threshold has been raised.
@@ -954,7 +967,7 @@
       // Build a new application with the Outliner class.
       originalApplication = processedApplication;
       processedApplication = processApplication(originalApplication, options);
-      assertEquals(count - 1 + 3, getNumberOfProgramClasses(processedApplication));
+      assertEquals(count * 3, getNumberOfProgramClasses(processedApplication));
     }
 
     // Run the application with several levels of outlining.
@@ -1195,7 +1208,6 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method1 = getMethod(processedApplication, signature1);
@@ -1205,7 +1217,7 @@
     assertTrue(code1.instructions[1] instanceof MoveResult);
     assertTrue(code1.instructions[2] instanceof Return);
     InvokeStatic invoke1 = (InvokeStatic) code1.instructions[0];
-    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod()));
 
     DexEncodedMethod method2 = getMethod(processedApplication, signature2);
     DexCode code2 = method2.getCode().asDexCode();
@@ -1287,7 +1299,7 @@
     assertTrue(code.instructions[5] instanceof Const4);
     assertTrue(code.instructions[6] instanceof Return);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod()));
 
     // Run code and check result.
     String result = runArt(processedApplication);
@@ -1614,15 +1626,13 @@
 
     AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
     AndroidApp processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Verify the code.
     runDex2Oat(processedApplication);
   }
 
   private static boolean isOutlineInvoke(Instruction instruction) {
-    return instruction instanceof InvokeStatic
-        && instruction.getMethod().holder.toSourceString().equals(OutlineOptions.CLASS_NAME);
+    return instruction instanceof InvokeStatic && isOutlineMethodName(instruction.getMethod());
   }
 
   private void assertHasOutlineInvoke(DexEncodedMethod method) {
@@ -1699,10 +1709,10 @@
     assertHasOutlineInvoke(getMethod(processedApplication, signature2));
     assertThat(
         new CodeInspector(processedApplication)
-            .clazz(OutlineOptions.CLASS_NAME)
+            .clazz(OUTLINE_CLASS)
             .method(
                 "boolean",
-                "outline0",
+                SyntheticItemsTestUtils.syntheticMethodName(),
                 ImmutableList.of("java.io.PrintStream", "java.util.ArrayList")),
         isPresent());
 
@@ -1774,8 +1784,11 @@
     assertHasOutlineInvoke(getMethod(processedApplication, signature2));
     assertThat(
         new CodeInspector(processedApplication)
-            .clazz(OutlineOptions.CLASS_NAME)
-            .method("boolean", "outline0", ImmutableList.of("java.util.List")),
+            .clazz(OUTLINE_CLASS)
+            .method(
+                "boolean",
+                SyntheticItemsTestUtils.syntheticMethodName(),
+                ImmutableList.of("java.util.List")),
         isPresent());
 
     // Run code and check result.
@@ -1848,8 +1861,11 @@
     assertHasOutlineInvoke(getMethod(processedApplication, signature2));
     assertThat(
         new CodeInspector(processedApplication)
-            .clazz(OutlineOptions.CLASS_NAME)
-            .method("boolean", "outline0", ImmutableList.of("java.util.ArrayList")),
+            .clazz(OUTLINE_CLASS)
+            .method(
+                "boolean",
+                SyntheticItemsTestUtils.syntheticMethodName(),
+                ImmutableList.of("java.util.ArrayList")),
         isPresent());
 
     // Run code and check result.
@@ -1922,8 +1938,11 @@
     assertHasOutlineInvoke(getMethod(processedApplication, signature2));
     assertThat(
         new CodeInspector(processedApplication)
-            .clazz(OutlineOptions.CLASS_NAME)
-            .method("boolean", "outline0", ImmutableList.of("java.util.ArrayList")),
+            .clazz(OUTLINE_CLASS)
+            .method(
+                "boolean",
+                SyntheticItemsTestUtils.syntheticMethodName(),
+                ImmutableList.of("java.util.ArrayList")),
         isPresent());
 
     // Run code and check result.
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 438d3f9..3f9c6af 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -16,6 +16,10 @@
 
 public class SyntheticItemsTestUtils {
 
+  public static String syntheticMethodName() {
+    return SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX;
+  }
+
   public static ClassReference syntheticCompanionClass(Class<?> clazz) {
     return Reference.classFromDescriptor(
         InterfaceMethodRewriter.getCompanionClassDescriptor(
@@ -23,8 +27,11 @@
   }
 
   private static ClassReference syntheticClass(Class<?> clazz, SyntheticKind kind, int id) {
-    return SyntheticNaming.makeSyntheticReferenceForTest(
-        Reference.classFromClass(clazz), kind, "" + id);
+    return syntheticClass(Reference.classFromClass(clazz), kind, id);
+  }
+
+  private static ClassReference syntheticClass(ClassReference clazz, SyntheticKind kind, int id) {
+    return SyntheticNaming.makeSyntheticReferenceForTest(clazz, kind, "" + id);
   }
 
   public static MethodReference syntheticBackportMethod(Class<?> clazz, int id, Method method) {
@@ -33,10 +40,18 @@
     MethodReference originalMethod = Reference.methodFromMethod(method);
     return Reference.methodFromDescriptor(
         syntheticHolder.getDescriptor(),
-        SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX,
+        syntheticMethodName(),
         originalMethod.getMethodDescriptor());
   }
 
+  public static ClassReference syntheticOutlineClass(Class<?> clazz, int id) {
+    return syntheticClass(clazz, SyntheticKind.OUTLINE, id);
+  }
+
+  public static ClassReference syntheticOutlineClass(ClassReference clazz, int id) {
+    return syntheticClass(clazz, SyntheticKind.OUTLINE, id);
+  }
+
   public static ClassReference syntheticLambdaClass(Class<?> clazz, int id) {
     return syntheticClass(clazz, SyntheticNaming.SyntheticKind.LAMBDA, id);
   }
@@ -67,6 +82,10 @@
     return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.TWR_CLOSE_RESOURCE);
   }
 
+  public static boolean isExternalOutlineClass(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.OUTLINE);
+  }
+
   public static Matcher<String> containsInternalSyntheticReference() {
     return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
   }