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 {