Add support for ServiceLoader usages in Enqueuer

Bug: 124181030
Change-Id: I68c339a41d25245cd3441cef54f3dc7715df6994
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index f174960..85da043 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.utils.OutputBuilder;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.IOException;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 77968e6..9c2a30c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -264,7 +264,7 @@
       AppView<AppInfoWithSubtyping> appView =
           new AppView<>(
               new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
-      appView.setAppServices(AppServices.builder(application).build());
+      appView.setAppServices(AppServices.builder(appView).build());
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       RootSet rootSet;
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index cfa5898..4c1881c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
@@ -25,29 +27,49 @@
 /** A description of the services and their implementations found in META-INF/services/. */
 public class AppServices {
 
+  // Mapping from service types to service implementation types.
   private final Map<DexType, Set<DexType>> services;
 
   private AppServices(Map<DexType, Set<DexType>> services) {
     this.services = services;
   }
 
-  public static Builder builder(DexApplication application) {
-    return new Builder(application);
+  public Set<DexType> allServiceTypes() {
+    return services.keySet();
+  }
+
+  public Set<DexType> serviceImplementationsFor(DexType serviceType) {
+    assert services.containsKey(serviceType);
+    Set<DexType> serviceImplementationTypes = services.get(serviceType);
+    if (serviceImplementationTypes == null) {
+      assert false
+          : "Unexpected attempt to get service implementations for non-service type `"
+              + serviceType.toSourceString()
+              + "`";
+      return ImmutableSet.of();
+    }
+    return serviceImplementationTypes;
+  }
+
+  public static Builder builder(AppView<? extends AppInfo> appView) {
+    return new Builder(appView);
   }
 
   public static class Builder {
 
     private static final String SERVICE_DIRECTORY_NAME = "META-INF/services/";
 
-    private final DexApplication app;
+    private final AppView<? extends AppInfo> appView;
     private final Map<DexType, Set<DexType>> services = new IdentityHashMap<>();
 
-    private Builder(DexApplication app) {
-      this.app = app;
+    private Builder(AppView<? extends AppInfo> appView) {
+      this.appView = appView;
     }
 
     public AppServices build() {
-      for (ProgramResourceProvider programResourceProvider : app.programResourceProviders) {
+      Iterable<ProgramResourceProvider> programResourceProviders =
+          appView.appInfo().app.programResourceProviders;
+      for (ProgramResourceProvider programResourceProvider : programResourceProviders) {
         DataResourceProvider dataResourceProvider =
             programResourceProvider.getDataResourceProvider();
         if (dataResourceProvider != null) {
@@ -80,10 +102,11 @@
             String serviceName = name.substring(SERVICE_DIRECTORY_NAME.length());
             if (DescriptorUtils.isValidJavaType(serviceName)) {
               String serviceDescriptor = DescriptorUtils.javaTypeToDescriptor(serviceName);
-              DexType serviceType = app.dexItemFactory.createType(serviceDescriptor);
+              DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
               byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
               String contents = new String(bytes, Charset.defaultCharset());
-              services.put(serviceType, readServiceImplementationsForService(contents));
+              services.put(
+                  serviceType, readServiceImplementationsForService(contents, file.getOrigin()));
             }
           }
         } catch (IOException | ResourceException e) {
@@ -91,14 +114,31 @@
         }
       }
 
-      private Set<DexType> readServiceImplementationsForService(String contents) {
+      private Set<DexType> readServiceImplementationsForService(String contents, Origin origin) {
         if (contents != null) {
           return Arrays.stream(contents.split(System.lineSeparator()))
               .map(String::trim)
               .filter(line -> !line.isEmpty())
               .filter(DescriptorUtils::isValidJavaType)
               .map(DescriptorUtils::javaTypeToDescriptor)
-              .map(app.dexItemFactory::createType)
+              .map(appView.dexItemFactory()::createType)
+              .filter(
+                  serviceImplementationType -> {
+                    if (!serviceImplementationType.isClassType()) {
+                      // Should never happen.
+                      appView
+                          .options()
+                          .reporter
+                          .warning(
+                              new StringDiagnostic(
+                                  "Unexpected service implementation found in META-INF/services/: `"
+                                      + serviceImplementationType.toSourceString()
+                                      + "`.",
+                                  origin));
+                      return false;
+                    }
+                    return true;
+                  })
               .collect(Collectors.toSet());
         }
         return ImmutableSet.of();
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e7b3967..3ce4a75 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -170,6 +170,7 @@
   public final DexString objectDescriptor = createString("Ljava/lang/Object;");
   public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
   public final DexString classDescriptor = createString("Ljava/lang/Class;");
+  public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
   public final DexString autoCloseableDescriptor = createString("Ljava/lang/AutoCloseable;");
   public final DexString classArrayDescriptor = createString("[Ljava/lang/Class;");
   public final DexString fieldDescriptor = createString("Ljava/lang/reflect/Field;");
@@ -187,6 +188,7 @@
   public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
 
   public final DexString npeDescriptor = createString("Ljava/lang/NullPointerException;");
+  public final DexString serviceLoaderDescriptor = createString("Ljava/util/ServiceLoader;");
 
   public final DexString intFieldUpdaterDescriptor =
       createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
@@ -237,6 +239,7 @@
   public final DexType exceptionInInitializerErrorType =
       createType(exceptionInInitializerErrorDescriptor);
   public final DexType classType = createType(classDescriptor);
+  public final DexType classLoaderType = createType(classLoaderDescriptor);
   public final DexType autoCloseableType = createType(autoCloseableDescriptor);
 
   public final DexType stringBuilderType = createType(stringBuilderDescriptor);
@@ -247,6 +250,7 @@
   public final DexType methodTypeType = createType(methodTypeDescriptor);
 
   public final DexType npeType = createType(npeDescriptor);
+  public final DexType serviceLoaderType = createType(serviceLoaderDescriptor);
 
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
@@ -266,6 +270,7 @@
       new AtomicFieldUpdaterMethods();
   public final Kotlin kotlin;
   public final PolymorphicMethods polymorphicMethods = new PolymorphicMethods();
+  public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
 
   public final DexString twrCloseResourceMethodName = createString("$closeResource");
   public final DexProto twrCloseResourceMethodProto =
@@ -790,6 +795,32 @@
     }
   }
 
+  public class ServiceLoaderMethods {
+
+    public final DexMethod load;
+    public final DexMethod loadWithClassLoader;
+    public final DexMethod loadInstalled;
+
+    private ServiceLoaderMethods() {
+      DexString loadName = createString("load");
+      load = createMethod(serviceLoaderType, createProto(serviceLoaderType, classType), loadName);
+      loadWithClassLoader =
+          createMethod(
+              serviceLoaderType,
+              createProto(serviceLoaderType, classType, classLoaderType),
+              loadName);
+      loadInstalled =
+          createMethod(
+              serviceLoaderType,
+              createProto(serviceLoaderType, classType),
+              createString("loadInstalled"));
+    }
+
+    public boolean isLoadMethod(DexMethod method) {
+      return method == load || method == loadWithClassLoader || method == loadInstalled;
+    }
+  }
+
   private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
     assert item != null;
     assert !DexItemFactory.isInternalSentinel(item);
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 1a19a39..f167226 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
@@ -492,6 +493,10 @@
       if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
         pendingReflectiveUses.add(currentMethod);
       }
+      // Handling of application services.
+      if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(method)) {
+        pendingReflectiveUses.add(currentMethod);
+      }
       if (!registerItemWithTargetAndContext(staticInvokes, method, currentMethod)) {
         return false;
       }
@@ -1688,6 +1693,13 @@
             collectReachedFields(staticFields, this::tryLookupStaticField)));
   }
 
+  private void markClassAsInstantiatedWithReason(DexClass clazz, KeepReason reason) {
+    workList.add(Action.markInstantiated(clazz, reason));
+    if (clazz.hasDefaultInitializer()) {
+      workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+    }
+  }
+
   private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
     ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
     proguardCompatibilityWorkList.add(
@@ -1728,6 +1740,10 @@
       handleJavaLangEnumValueOf(method, invoke);
       return;
     }
+    if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) {
+      handleServiceLoaderInvocation(method, invoke);
+      return;
+    }
     if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
       return;
     }
@@ -1797,6 +1813,53 @@
     }
   }
 
+  private void handleServiceLoaderInvocation(DexEncodedMethod method, InvokeMethod invoke) {
+    if (invoke.inValues().size() == 0) {
+      // Should never happen.
+      return;
+    }
+
+    Value argument = invoke.inValues().get(0).getAliasedValue();
+    if (!argument.isPhi() && argument.definition.isConstClass()) {
+      DexType serviceType = argument.definition.asConstClass().getValue();
+      if (!appView.appServices().allServiceTypes().contains(serviceType)) {
+        // Should never happen.
+        options.reporter.warning(
+            new StringDiagnostic(
+                "The type `"
+                    + serviceType.toSourceString()
+                    + "` is being passed to the method `"
+                    + invoke.getInvokedMethod()
+                    + "`, but could was found in `META-INF/services/`.",
+                appInfo.originFor(method.method.holder)));
+        return;
+      }
+
+      handleServiceInstantiation(serviceType, KeepReason.reflectiveUseIn(method));
+    } else {
+      KeepReason reason = KeepReason.reflectiveUseIn(method);
+      for (DexType serviceType : appView.appServices().allServiceTypes()) {
+        handleServiceInstantiation(serviceType, reason);
+      }
+    }
+  }
+
+  private void handleServiceInstantiation(DexType serviceType, KeepReason reason) {
+    Set<DexType> serviceImplementationTypes =
+        appView.appServices().serviceImplementationsFor(serviceType);
+    for (DexType serviceImplementationType : serviceImplementationTypes) {
+      if (!serviceImplementationType.isClassType()) {
+        // Should never happen.
+        continue;
+      }
+
+      DexClass serviceImplementationClass = appInfo.definitionFor(serviceImplementationType);
+      if (serviceImplementationClass != null && serviceImplementationClass.isProgramClass()) {
+        markClassAsInstantiatedWithReason(serviceImplementationClass, reason);
+      }
+    }
+  }
+
   private static class Action {
 
     final Kind kind;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index eb9916e..a767c86 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -57,9 +57,11 @@
     private Position keepParameterNamesOptionPosition;
     private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
     private final ProguardPathFilter.Builder adaptResourceFilenames =
-        ProguardPathFilter.builder().disable();
+        ProguardPathFilter.builder()
+            .addPattern(ProguardPathList.builder().addFileName("META-INF/services/*").build());
     private final ProguardPathFilter.Builder adaptResourceFileContents =
-        ProguardPathFilter.builder().disable();
+        ProguardPathFilter.builder()
+            .addPattern(ProguardPathList.builder().addFileName("META-INF/services/*").build());
     private final ProguardPathFilter.Builder keepDirectories =
         ProguardPathFilter.builder().disable();
     private boolean forceProguardCompatibility = false;
@@ -243,18 +245,10 @@
       adaptClassStrings.addPattern(pattern);
     }
 
-    public void enableAdaptResourceFilenames() {
-      adaptResourceFilenames.enable();
-    }
-
     public void addAdaptResourceFilenames(ProguardPathList pattern) {
       adaptResourceFilenames.addPattern(pattern);
     }
 
-    public void enableAdaptResourceFileContents() {
-      adaptResourceFileContents.enable();
-    }
-
     public void addAdaptResourceFileContents(ProguardPathList pattern) {
       adaptResourceFileContents.addPattern(pattern);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5acd2a8..eaad9a4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -384,10 +384,8 @@
       } else if (acceptString("adaptclassstrings")) {
         parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
       } else if (acceptString("adaptresourcefilenames")) {
-        configurationBuilder.enableAdaptResourceFilenames();
         parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
       } else if (acceptString("adaptresourcefilecontents")) {
-        configurationBuilder.enableAdaptResourceFileContents();
         parsePathFilter(configurationBuilder::addAdaptResourceFileContents);
       } else if (acceptString("identifiernamestring")) {
         configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
@@ -1710,7 +1708,7 @@
       if (fileFilter == null) {
         throw parseError("Path filter expected");
       }
-      builder.addFileName(negated, fileFilter);
+      builder.addFileName(fileFilter, negated);
       skipWhitespace();
       while (acceptChar(',')) {
         skipWhitespace();
@@ -1720,7 +1718,7 @@
         if (fileFilter == null) {
           throw parseError("Path filter expected");
         }
-        builder.addFileName(negated, fileFilter);
+        builder.addFileName(fileFilter, negated);
         skipWhitespace();
       }
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java b/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
index 6766128..edcc4f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
@@ -26,7 +26,11 @@
     private Builder() {
     }
 
-    public Builder addFileName(boolean isNegated, String path) {
+    public Builder addFileName(String path) {
+      return addFileName(path, false);
+    }
+
+    public Builder addFileName(String path, boolean isNegated) {
       matchers.add(new FileNameMatcher(isNegated, path));
       return this;
     }
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 0f3accb..35b6b4c 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestCompileResult;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
@@ -60,13 +59,24 @@
     this.backend = backend;
   }
 
-  protected static class CustomDataResourceConsumer implements DataResourceConsumer {
+  public static class DataResourceConsumerForTesting implements DataResourceConsumer {
 
+    private final DataResourceConsumer inner;
     private final Map<String, ImmutableList<String>> resources = new HashMap<>();
 
+    public DataResourceConsumerForTesting() {
+      this(null);
+    }
+
+    public DataResourceConsumerForTesting(DataResourceConsumer inner) {
+      this.inner = inner;
+    }
+
     @Override
     public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
-      throw new Unreachable();
+      if (inner != null) {
+        inner.accept(directory, diagnosticsHandler);
+      }
     }
 
     @Override
@@ -79,6 +89,9 @@
       } catch (Exception e) {
         throw new RuntimeException(e);
       }
+      if (inner != null) {
+        inner.accept(file, diagnosticsHandler);
+      }
     }
 
     @Override
@@ -175,7 +188,7 @@
   @Test
   public void testEnabled() throws Exception {
     String pgConf = getProguardConfigWithNeverInline(true, null);
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     CodeInspector inspector = compileWithR8(pgConf, dataResourceConsumer).inspector();
 
     // Check that the data resources have changed as expected.
@@ -257,7 +270,7 @@
 
   @Test
   public void testEnabledWithFilter() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(true, "*.md"), dataResourceConsumer);
 
     // Check that the file matching the filter has changed as expected.
@@ -279,7 +292,7 @@
 
   @Test
   public void testDisabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
 
     // Check that all data resources are unchanged.
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 437f2dd..40f4249 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.naming.AdaptResourceFileContentsTest.CustomDataResourceConsumer;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
@@ -107,7 +107,7 @@
 
   @Test
   public void testEnabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, null), dataResourceConsumer, this::checkR8Renamings);
     // Check that the generated resources have the expected names.
@@ -120,7 +120,7 @@
 
   @Test
   public void testEnabledWithFilter() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, "**.md"),
         dataResourceConsumer,
@@ -138,7 +138,7 @@
 
   @Test
   public void testDisabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
     // Check that none of the resources were renamed.
     for (DataEntryResource dataResource : getOriginalDataResources()) {
@@ -150,7 +150,7 @@
 
   @Test
   public void testCollisionBehavior() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, null),
         dataResourceConsumer,
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index e005a2c..d856f1d 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -4,11 +4,17 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.BooleanUtils;
 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.google.common.collect.Lists;
 import java.util.List;
@@ -23,6 +29,8 @@
 
   private final boolean includeWorldGreeter;
 
+  private DataResourceConsumerForTesting dataResourceConsumer;
+
   @Parameters(name = "Include WorldGreeter: {0}")
   public static Boolean[] data() {
     return BooleanUtils.values();
@@ -45,23 +53,49 @@
         testForR8(Backend.DEX)
             .addInnerClasses(ServiceLoaderTest.class)
             .addKeepMainRule(TestClass.class)
-            // TODO(b/124181030): Test should work without the following keep-all-rules.
-            .addKeepAllClassesRule()
-            .addKeepAllInterfacesRule()
+            // TODO(b/124181030): It should not be necessary to keep Greeter, but the resource
+            //  adapter needs to rewrite the resource file names.
+            .addKeepRules("-keep interface " + Greeter.class.getTypeName())
             .addDataEntryResources(
                 DataEntryResource.fromBytes(
                     StringUtils.lines(serviceImplementations).getBytes(),
                     "META-INF/services/" + Greeter.class.getTypeName(),
                     Origin.unknown()))
+            .addOptionsModification(
+                options -> {
+                  dataResourceConsumer =
+                      new DataResourceConsumerForTesting(options.dataResourceConsumer);
+                  options.dataResourceConsumer = dataResourceConsumer;
+                })
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
-    // TODO(b/124181030): Verify that Greeter is merged into HelloGreeter when `includeWorldGreeter`
-    //  is false.
+    ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+    // TODO(b/124181030): Greeter should be merged into HelloGreeter when the keep rule above is
+    //  removed.
+    assertThat(greeterSubject, isPresent());
 
-    // TODO(b/124181030): Verify that META-INF/services/...WorldGreeter is removed when
-    //  `includeWorldGreeter` is false.
+    ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
+    assertThat(helloGreeterSubject, isPresent());
+
+    ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
+    assertEquals(includeWorldGreeter, worldGreeterSubject.isPresent());
+
+    // TODO(b/124181030): The resource file name should become:
+    //  `includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName()`.
+    List<String> lines =
+        dataResourceConsumer.get("META-INF/services/" + greeterSubject.getOriginalName());
+    assertEquals(includeWorldGreeter ? 2 : 1, lines.size());
+    assertEquals(helloGreeterSubject.getFinalName(), lines.get(0));
+    if (includeWorldGreeter) {
+      assertEquals(worldGreeterSubject.getFinalName(), lines.get(1));
+    }
+
+    // TODO(b/124181030): Verify that META-INF/services/...Greeter is removed if there is no call to
+    //  ServiceLoader.load().
+
+    // TODO(b/124181030): Verify that -whyareyoukeeping works as intended.
   }
 
   static class TestClass {