Version 1.4.47
Cherry pick: Add service loader test
CL: https://r8-review.googlesource.com/34420
Cherry pick: Read services from META-INF/services/
CL: https://r8-review.googlesource.com/34469
Cherry pick: Add support for ServiceLoader usages in Enqueuer
CL: https://r8-review.googlesource.com/34470
Cherry pick: Renaming and elimination of services in META-INF/services/
CL: https://r8-review.googlesource.com/34474
Cherry pick: Update AndroidAppConsumers to avoid "zip file closed" issue
CL: https://r8-review.googlesource.com/34475
Cherry pick: Update AndroidAppConsumers to avoid "zip file closed" issue for CF
CL: https://r8-review.googlesource.com/34476
Cherry pick: Attempt to fix tests
CL: https://r8-review.googlesource.com/c/r8/+/34477
Bug: 124181030
Change-Id: I51ac9631902f35f176a5d2d89beb4af1b83dba77
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 364a794..e124571 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -18,6 +18,7 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
+import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -150,7 +151,8 @@
return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
}
- public static void writeResources(Path archive, List<ProgramResource> resources)
+ public static void writeResources(
+ Path archive, List<ProgramResource> resources, Set<DataEntryResource> dataResources)
throws IOException, ResourceException {
OpenOption[] options =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -163,6 +165,11 @@
byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
}
+ for (DataEntryResource dataResource : dataResources) {
+ String entryName = dataResource.getName();
+ byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
+ ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 466a26f..2273c4d 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -204,6 +204,7 @@
new ApplicationWriter(
app,
+ null,
options,
marker == null ? null : ImmutableList.copyOf(markers),
null,
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index b6bb4e0..6974d46 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -95,6 +95,7 @@
ApplicationWriter writer =
new ApplicationWriter(
app,
+ null,
options,
markers,
null,
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index e235e67..3ca4bf2 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -179,7 +179,8 @@
outputBuilder.close(handler);
}
- public static void writeResources(Path archive, List<ProgramResource> resources)
+ public static void writeResources(
+ Path archive, List<ProgramResource> resources, Set<DataEntryResource> dataResources)
throws IOException, ResourceException {
OpenOption[] options =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -191,6 +192,11 @@
byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
}
+ for (DataEntryResource dataResource : dataResources) {
+ String entryName = dataResource.getName();
+ byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
+ ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index f78912f..f7e5649 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -95,6 +95,7 @@
try {
new ApplicationWriter(
featureApp,
+ null,
options,
markers,
null,
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 049854e..37311e8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,7 +11,9 @@
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.AppliedGraphLens;
import com.android.tools.r8.graph.DexApplication;
@@ -182,6 +184,7 @@
static void writeApplication(
ExecutorService executorService,
DexApplication application,
+ AppView<? extends AppInfo> appView,
String deadCode,
GraphLense graphLense,
NamingLens namingLens,
@@ -194,6 +197,7 @@
if (options.isGeneratingClassFiles()) {
new CfApplicationWriter(
application,
+ appView,
options,
deadCode,
graphLense,
@@ -204,6 +208,7 @@
} else {
new ApplicationWriter(
application,
+ appView,
options,
marker == null ? null : Collections.singletonList(marker),
deadCode,
@@ -259,6 +264,8 @@
AppView<AppInfoWithSubtyping> appView =
new AppView<>(
new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
+ appView.setAppServices(AppServices.builder(appView).build());
+
List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
RootSet rootSet;
String proguardSeedsData = null;
@@ -479,6 +486,8 @@
new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
}
+ appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLense()));
+
timing.begin("Create IR");
Set<DexCallSite> desugaredCallSites;
CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
@@ -667,6 +676,7 @@
writeApplication(
executorService,
application,
+ appView,
application.deadCode,
appView.graphLense(),
namingLens,
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 40cfef9..a3b2a90 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.46";
+ public static final String LABEL = "1.4.47";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 60167c5..2b661d0 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -178,7 +178,7 @@
InternalOptions options = new InternalOptions();
AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
ApplicationWriter writer =
- new ApplicationWriter(app, options, null, null, null, null, null, null);
+ new ApplicationWriter(app, null, options, null, null, null, null, null, null);
writer.write(executor);
compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 24e82a7..c485a47 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -59,6 +61,7 @@
public class ApplicationWriter {
public final DexApplication application;
+ public final AppView<? extends AppInfo> appView;
public final String deadCode;
public final GraphLense graphLense;
public final NamingLens namingLens;
@@ -127,6 +130,7 @@
public ApplicationWriter(
DexApplication application,
+ AppView<? extends AppInfo> appView,
InternalOptions options,
List<Marker> markers,
String deadCode,
@@ -136,6 +140,7 @@
ProguardMapSupplier proguardMapSupplier) {
this(
application,
+ appView,
options,
markers,
deadCode,
@@ -148,6 +153,7 @@
public ApplicationWriter(
DexApplication application,
+ AppView<? extends AppInfo> appView,
InternalOptions options,
List<Marker> markers,
String deadCode,
@@ -158,6 +164,7 @@
DexIndexedConsumer consumer) {
assert application != null;
this.application = application;
+ this.appView = appView;
assert options != null;
this.options = options;
if (markers != null && !markers.isEmpty()) {
@@ -284,6 +291,7 @@
// Supply info to all additional resource consumers.
supplyAdditionalConsumers(
application,
+ appView,
graphLense,
namingLens,
options,
@@ -297,6 +305,7 @@
public static void supplyAdditionalConsumers(
DexApplication application,
+ AppView<? extends AppInfo> appView,
GraphLense graphLense,
NamingLens namingLens,
InternalOptions options,
@@ -338,7 +347,7 @@
.collect(Collectors.toList());
ResourceAdapter resourceAdapter =
- new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+ new ResourceAdapter(appView, application.dexItemFactory, graphLense, namingLens, options);
Set<String> generatedResourceNames = new HashSet<>();
for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
@@ -356,6 +365,10 @@
@Override
public void visit(DataEntryResource file) {
+ if (resourceAdapter.shouldBeDeleted(file)) {
+ return;
+ }
+
DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
if (generatedResourceNames.add(adapted.getName())) {
dataResourceConsumer.accept(adapted, options.reporter);
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index ae59576..630f25d 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -8,11 +8,15 @@
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardPathFilter;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -27,16 +31,19 @@
public class ResourceAdapter {
+ private final AppView<? extends AppInfo> appView;
private final DexItemFactory dexItemFactory;
private final GraphLense graphLense;
private final NamingLens namingLense;
private final InternalOptions options;
public ResourceAdapter(
+ AppView<? extends AppInfo> appView,
DexItemFactory dexItemFactory,
GraphLense graphLense,
NamingLens namingLense,
InternalOptions options) {
+ this.appView = appView;
this.dexItemFactory = dexItemFactory;
this.graphLense = graphLense;
this.namingLense = namingLense;
@@ -88,8 +95,40 @@
return DataDirectoryResource.fromName(adaptDirectoryName(directory), directory.getOrigin());
}
+ // Returns true for files in META-INF/services/ that are never used by the application.
+ public boolean shouldBeDeleted(DataEntryResource file) {
+ if (appView != null && appView.appInfo().hasLiveness()) {
+ AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+ if (file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)) {
+ String serviceName = file.getName().substring(AppServices.SERVICE_DIRECTORY_NAME.length());
+ if (!DescriptorUtils.isValidJavaType(serviceName)) {
+ return false;
+ }
+
+ DexString serviceDescriptor =
+ dexItemFactory.lookupString(DescriptorUtils.javaTypeToDescriptor(serviceName));
+ if (serviceDescriptor == null) {
+ return false;
+ }
+
+ DexType serviceType = appView.dexItemFactory().lookupType(serviceDescriptor);
+ if (serviceType == null) {
+ return false;
+ }
+
+ DexType rewrittenServiceType = appView.graphLense().lookupType(serviceType);
+ assert appView.appServices().allServiceTypes().contains(rewrittenServiceType);
+ return !appInfo.instantiatedAppServices.contains(rewrittenServiceType);
+ }
+ }
+ return false;
+ }
+
private String adaptFileName(DataEntryResource file) {
- FileNameAdapter adapter = new FileNameAdapter(file.getName());
+ FileNameAdapter adapter =
+ file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)
+ ? new ServiceFileNameAdapter(file.getName())
+ : new DefaultFileNameAdapter(file.getName());
if (adapter.run()) {
return adapter.getResult();
}
@@ -307,7 +346,7 @@
private void outputRangeFromInput(int from, int toExclusive) {
if (from < toExclusive) {
- result.append(contents.substring(from, toExclusive));
+ result.append(contents, from, toExclusive);
}
}
@@ -362,8 +401,8 @@
}
}
- private abstract class FileNameAdapterBase extends StringAdapter {
- public FileNameAdapterBase(String filename) {
+ private abstract class FileNameAdapter extends StringAdapter {
+ public FileNameAdapter(String filename) {
super(filename);
}
@@ -391,8 +430,8 @@
}
}
- private class FileNameAdapter extends FileNameAdapterBase {
- public FileNameAdapter(String filename) {
+ private class DefaultFileNameAdapter extends FileNameAdapter {
+ public DefaultFileNameAdapter(String filename) {
super(filename);
}
@@ -402,7 +441,28 @@
}
}
- private class DirectoryNameAdapter extends FileNameAdapterBase {
+ private class ServiceFileNameAdapter extends FileNameAdapter {
+ public ServiceFileNameAdapter(String filename) {
+ super(filename);
+ }
+
+ @Override
+ public char getClassNameSeparator() {
+ return '.';
+ }
+
+ @Override
+ public boolean allowRenamingOfPrefixes() {
+ return false;
+ }
+
+ @Override
+ public boolean isRenamingCandidate(int from, int toExclusive) {
+ return from == AppServices.SERVICE_DIRECTORY_NAME.length() && eof(toExclusive);
+ }
+ }
+
+ private class DirectoryNameAdapter extends FileNameAdapter {
public DirectoryNameAdapter(String filename) {
super(filename);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
new file mode 100644
index 0000000..ec22d85
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -0,0 +1,178 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+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.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** A description of the services and their implementations found in META-INF/services/. */
+public class AppServices {
+
+ public static final String SERVICE_DIRECTORY_NAME = "META-INF/services/";
+
+ private final AppView<? extends AppInfo> appView;
+
+ // Mapping from service types to service implementation types.
+ private final Map<DexType, Set<DexType>> services;
+
+ private AppServices(AppView<? extends AppInfo> appView, Map<DexType, Set<DexType>> services) {
+ this.appView = appView;
+ this.services = services;
+ }
+
+ public Set<DexType> allServiceTypes() {
+ assert verifyRewrittenWithLens();
+ return services.keySet();
+ }
+
+ public Set<DexType> serviceImplementationsFor(DexType serviceType) {
+ assert verifyRewrittenWithLens();
+ 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 AppServices rewrittenWithLens(GraphLense graphLens) {
+ ImmutableMap.Builder<DexType, Set<DexType>> rewrittenServices = ImmutableMap.builder();
+ for (Entry<DexType, Set<DexType>> entry : services.entrySet()) {
+ DexType rewrittenServiceType = graphLens.lookupType(entry.getKey());
+ ImmutableSet.Builder<DexType> rewrittenServiceImplementationTypes = ImmutableSet.builder();
+ for (DexType serviceImplementationType : entry.getValue()) {
+ rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+ }
+ rewrittenServices.put(rewrittenServiceType, rewrittenServiceImplementationTypes.build());
+ }
+ return new AppServices(appView, rewrittenServices.build());
+ }
+
+ private boolean verifyRewrittenWithLens() {
+ for (Entry<DexType, Set<DexType>> entry : services.entrySet()) {
+ assert entry.getKey() == appView.graphLense().lookupType(entry.getKey());
+ for (DexType type : entry.getValue()) {
+ assert type == appView.graphLense().lookupType(type);
+ }
+ }
+ return true;
+ }
+
+ public static Builder builder(AppView<? extends AppInfo> appView) {
+ return new Builder(appView);
+ }
+
+ public static class Builder {
+
+ private final AppView<? extends AppInfo> appView;
+ private final Map<DexType, Set<DexType>> services = new IdentityHashMap<>();
+
+ private Builder(AppView<? extends AppInfo> appView) {
+ this.appView = appView;
+ }
+
+ public AppServices build() {
+ Iterable<ProgramResourceProvider> programResourceProviders =
+ appView.appInfo().app.programResourceProviders;
+ for (ProgramResourceProvider programResourceProvider : programResourceProviders) {
+ DataResourceProvider dataResourceProvider =
+ programResourceProvider.getDataResourceProvider();
+ if (dataResourceProvider != null) {
+ readServices(dataResourceProvider);
+ }
+ }
+ return new AppServices(appView, services);
+ }
+
+ private void readServices(DataResourceProvider dataResourceProvider) {
+ try {
+ dataResourceProvider.accept(new DataResourceProviderVisitor());
+ } catch (ResourceException e) {
+ throw new CompilationError(e.getMessage(), e);
+ }
+ }
+
+ private class DataResourceProviderVisitor implements Visitor {
+
+ @Override
+ public void visit(DataDirectoryResource directory) {
+ // Ignore.
+ }
+
+ @Override
+ public void visit(DataEntryResource file) {
+ try {
+ String name = file.getName();
+ if (name.startsWith(SERVICE_DIRECTORY_NAME)) {
+ String serviceName = name.substring(SERVICE_DIRECTORY_NAME.length());
+ if (DescriptorUtils.isValidJavaType(serviceName)) {
+ String serviceDescriptor = DescriptorUtils.javaTypeToDescriptor(serviceName);
+ DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
+ byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+ String contents = new String(bytes, Charset.defaultCharset());
+ services.put(
+ serviceType, readServiceImplementationsForService(contents, file.getOrigin()));
+ }
+ }
+ } catch (IOException | ResourceException e) {
+ throw new CompilationError(e.getMessage(), e);
+ }
+ }
+
+ 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(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/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index e232cb9..084ad80 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -11,6 +11,7 @@
public class AppView<T extends AppInfo> {
private T appInfo;
+ private AppServices appServices;
private final DexItemFactory dexItemFactory;
private GraphLense graphLense;
private final InternalOptions options;
@@ -31,6 +32,14 @@
this.appInfo = appInfo;
}
+ public AppServices appServices() {
+ return appServices;
+ }
+
+ public void setAppServices(AppServices appServices) {
+ this.appServices = appServices;
+ }
+
public DexItemFactory dexItemFactory() {
return dexItemFactory;
}
@@ -86,6 +95,16 @@
}
@Override
+ public AppServices appServices() {
+ return AppView.this.appServices();
+ }
+
+ @Override
+ public void setAppServices(AppServices appServices) {
+ AppView.this.setAppServices(appServices);
+ }
+
+ @Override
public DexItemFactory dexItemFactory() {
return AppView.this.dexItemFactory();
}
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 7a33ba4..8bdf115 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -163,6 +163,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;");
@@ -178,6 +179,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;");
@@ -226,6 +228,7 @@
public final DexType annotationType = createType(annotationDescriptor);
public final DexType throwableType = createType(throwableDescriptor);
public final DexType classType = createType(classDescriptor);
+ public final DexType classLoaderType = createType(classLoaderDescriptor);
public final DexType autoCloseableType = createType(autoCloseableDescriptor);
public final DexType stringBuilderType = createType(stringBuilderDescriptor);
@@ -236,6 +239,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);
@@ -255,6 +259,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 =
@@ -753,6 +758,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/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 14459fe..7aa3ace 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -10,6 +10,8 @@
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
@@ -41,7 +43,6 @@
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
-import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.ExecutorService;
@@ -62,6 +63,7 @@
private static final boolean PRINT_CF = false;
private final DexApplication application;
+ private final AppView<? extends AppInfo> appView;
private final GraphLense graphLense;
private final NamingLens namingLens;
private final InternalOptions options;
@@ -72,6 +74,7 @@
public CfApplicationWriter(
DexApplication application,
+ AppView<? extends AppInfo> appView,
InternalOptions options,
String deadCode,
GraphLense graphLense,
@@ -79,6 +82,7 @@
String proguardSeedsData,
ProguardMapSupplier proguardMapSupplier) {
this.application = application;
+ this.appView = appView;
this.graphLense = graphLense;
this.namingLens = namingLens;
this.options = options;
@@ -87,7 +91,7 @@
this.proguardSeedsData = proguardSeedsData;
}
- public void write(ClassFileConsumer consumer, ExecutorService executor) throws IOException {
+ public void write(ClassFileConsumer consumer, ExecutorService executor) {
application.timing.begin("CfApplicationWriter.write");
try {
writeApplication(consumer, executor);
@@ -96,8 +100,7 @@
}
}
- private void writeApplication(ClassFileConsumer consumer, ExecutorService executor)
- throws IOException {
+ private void writeApplication(ClassFileConsumer consumer, ExecutorService executor) {
for (DexProgramClass clazz : application.classes()) {
if (clazz.getSynthesizedFrom().isEmpty()) {
writeClass(clazz, consumer);
@@ -107,6 +110,7 @@
}
ApplicationWriter.supplyAdditionalConsumers(
application,
+ appView,
graphLense,
namingLens,
options,
@@ -115,7 +119,7 @@
proguardSeedsData);
}
- private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
+ private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) {
ClassWriter writer = new ClassWriter(0);
writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
int version = getClassFileVersion(clazz);
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 a51ca83..076e021 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;
@@ -220,6 +221,12 @@
new SetWithReason<>(this::registerField);
/**
+ * Set of service types (from META-INF/services/) that may have been instantiated reflectively via
+ * ServiceLoader.load() or ServiceLoader.loadInstalled().
+ */
+ private final Set<DexType> instantiatedAppServices = Sets.newIdentityHashSet();
+
+ /**
* Set of interface types for which a lambda expression can be reached. These never have a single
* interface implementation.
*/
@@ -488,6 +495,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;
}
@@ -1674,6 +1685,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(
@@ -1714,6 +1732,10 @@
handleJavaLangEnumValueOf(method, invoke);
return;
}
+ if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) {
+ handleServiceLoaderInvocation(method, invoke);
+ return;
+ }
if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
return;
}
@@ -1783,6 +1805,55 @@
}
}
+ 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) {
+ instantiatedAppServices.add(serviceType);
+
+ 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;
@@ -1856,6 +1927,11 @@
*/
final SortedSet<DexType> instantiatedAnnotations;
/**
+ * Set of service types (from META-INF/services/) that may have been instantiated reflectively
+ * via ServiceLoader.load() or ServiceLoader.loadInstalled().
+ */
+ public final SortedSet<DexType> instantiatedAppServices;
+ /**
* Set of types that are actually instantiated. These cannot be abstract.
*/
final SortedSet<DexType> instantiatedTypes;
@@ -2015,6 +2091,9 @@
PresortedComparable<DexType>::slowCompareTo, enqueuer.liveTypes);
this.instantiatedAnnotations = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAnnotations);
+ this.instantiatedAppServices =
+ ImmutableSortedSet.copyOf(
+ PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAppServices);
this.instantiatedTypes = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
this.instantiatedLambdas =
@@ -2073,6 +2152,7 @@
super(application);
this.liveTypes = previous.liveTypes;
this.instantiatedAnnotations = previous.instantiatedAnnotations;
+ this.instantiatedAppServices = previous.instantiatedAppServices;
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
@@ -2122,6 +2202,8 @@
this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
this.instantiatedAnnotations =
rewriteItems(previous.instantiatedAnnotations, lense::lookupType);
+ this.instantiatedAppServices =
+ rewriteItems(previous.instantiatedAppServices, lense::lookupType);
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
@@ -2200,6 +2282,7 @@
super(previous);
this.liveTypes = previous.liveTypes;
this.instantiatedAnnotations = previous.instantiatedAnnotations;
+ this.instantiatedAppServices = previous.instantiatedAppServices;
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
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 709d27a..c2f4849 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));
@@ -1680,7 +1678,7 @@
if (fileFilter == null) {
throw parseError("Path filter expected");
}
- builder.addFileName(negated, fileFilter);
+ builder.addFileName(fileFilter, negated);
skipWhitespace();
while (acceptChar(',')) {
skipWhitespace();
@@ -1690,7 +1688,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/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 28d1628..4c15524 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResource;
import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DirectoryClassFileProvider;
@@ -31,6 +32,7 @@
import com.android.tools.r8.shaking.FilteredClassPath;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
@@ -38,10 +40,12 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
/**
* Collection of program files needed for processing.
@@ -213,6 +217,36 @@
}
}
+ public Set<DataEntryResource> getDataEntryResourcesForTesting() throws ResourceException {
+ Set<DataEntryResource> out = new TreeSet<>(Comparator.comparing(DataResource::getName));
+ for (ProgramResourceProvider programResourceProvider : getProgramResourceProviders()) {
+ DataResourceProvider dataResourceProvider = programResourceProvider.getDataResourceProvider();
+ if (dataResourceProvider != null) {
+ dataResourceProvider.accept(
+ new Visitor() {
+
+ @Override
+ public void visit(DataDirectoryResource directory) {
+ // Ignore.
+ }
+
+ @Override
+ public void visit(DataEntryResource file) {
+ try {
+ byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+ DataEntryResource copy =
+ DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+ out.add(copy);
+ } catch (IOException | ResourceException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+ }
+ return out;
+ }
+
/** Get program resource providers. */
public List<ProgramResourceProvider> getProgramResourceProviders() {
return programResourceProviders;
@@ -320,21 +354,19 @@
}
}
- /**
- * Write the dex program resources to @code{archive} and the proguard resource as its sibling.
- */
+ /** Write the dex program resources to @code{archive}. */
public void writeToZip(Path archive, OutputMode outputMode) throws IOException {
try {
if (outputMode == OutputMode.DexIndexed) {
- List<ProgramResource> resources = getDexProgramResourcesForTesting();
- DexIndexedConsumer.ArchiveConsumer.writeResources(archive, resources);
+ DexIndexedConsumer.ArchiveConsumer.writeResources(
+ archive, getDexProgramResourcesForTesting(), getDataEntryResourcesForTesting());
} else if (outputMode == OutputMode.DexFilePerClassFile) {
List<ProgramResource> resources = getDexProgramResourcesForTesting();
DexFilePerClassFileConsumer.ArchiveConsumer.writeResources(
archive, resources, programResourcesMainDescriptor);
} else if (outputMode == OutputMode.ClassFile) {
- List<ProgramResource> resources = getClassProgramResourcesForTesting();
- ClassFileConsumer.ArchiveConsumer.writeResources(archive, resources);
+ ClassFileConsumer.ArchiveConsumer.writeResources(
+ archive, getClassProgramResourcesForTesting(), getDataEntryResourcesForTesting());
} else {
throw new Unreachable("Unsupported output-mode for writing: " + outputMode);
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
index 4371edd..b7beb82 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -6,15 +6,21 @@
import com.android.tools.r8.BaseCompilerCommand;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.ResourceException;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.origin.Origin;
+import com.google.common.io.ByteStreams;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -105,6 +111,44 @@
}
}
+ @Override
+ public DataResourceConsumer getDataResourceConsumer() {
+ DataResourceConsumer dataResourceConsumer =
+ consumer != null ? consumer.getDataResourceConsumer() : null;
+ return new DataResourceConsumer() {
+
+ @Override
+ public void accept(
+ DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.accept(directory, diagnosticsHandler);
+ }
+ }
+
+ @Override
+ public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+ try {
+ byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+ DataEntryResource copy =
+ DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+ builder.addDataResource(copy);
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.accept(copy, diagnosticsHandler);
+ }
+ } catch (IOException | ResourceException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.finished(handler);
+ }
+ }
+ };
+ }
+
synchronized void addDexFile(int fileIndex, byte[] data, Set<String> descriptors) {
files.put(fileIndex, new DescriptorsWithContents(descriptors, data));
}
@@ -148,6 +192,44 @@
assert getDataResourceConsumer() != null;
}
}
+
+ @Override
+ public DataResourceConsumer getDataResourceConsumer() {
+ DataResourceConsumer dataResourceConsumer =
+ consumer != null ? consumer.getDataResourceConsumer() : null;
+ return new DataResourceConsumer() {
+
+ @Override
+ public void accept(
+ DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.accept(directory, diagnosticsHandler);
+ }
+ }
+
+ @Override
+ public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+ try {
+ byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+ DataEntryResource copy =
+ DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+ builder.addDataResource(copy);
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.accept(copy, diagnosticsHandler);
+ }
+ } catch (IOException | ResourceException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.finished(handler);
+ }
+ }
+ };
+ }
};
programConsumer = wrapped;
return wrapped;
@@ -182,6 +264,44 @@
assert getDataResourceConsumer() != null;
}
}
+
+ @Override
+ public DataResourceConsumer getDataResourceConsumer() {
+ DataResourceConsumer dataResourceConsumer =
+ consumer != null ? consumer.getDataResourceConsumer() : null;
+ return new DataResourceConsumer() {
+
+ @Override
+ public void accept(
+ DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.accept(directory, diagnosticsHandler);
+ }
+ }
+
+ @Override
+ public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+ try {
+ byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+ DataEntryResource copy =
+ DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+ builder.addDataResource(copy);
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.accept(copy, diagnosticsHandler);
+ }
+ } catch (IOException | ResourceException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.finished(handler);
+ }
+ }
+ };
+ }
};
programConsumer = wrapped;
return wrapped;
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 244639e..c91c288 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -141,7 +141,7 @@
return builder.toString();
}
- public static String lines(String... lines) {
+ public static String lines(List<String> lines) {
StringBuilder builder = new StringBuilder();
for (String line : lines) {
builder.append(line).append(LINE_SEPARATOR);
@@ -149,6 +149,10 @@
return builder.toString();
}
+ public static String lines(String... lines) {
+ return lines(Arrays.asList(lines));
+ }
+
public static String withNativeLineSeparator(String s) {
s = s.replace("\r\n", "\n");
if (LINE_SEPARATOR.equals("\r\n")) {
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index e9d85be..7cbd89f 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -121,6 +121,11 @@
}
@Override
+ public ExternalR8TestBuilder addDataEntryResources(DataEntryResource... resources) {
+ throw new Unimplemented("No support for adding data entry resources");
+ }
+
+ @Override
public ExternalR8TestBuilder addProgramClasses(Collection<Class<?>> classes) {
// Adding a collection of classes will build a jar of exactly those classes so that no other
// classes are made available via a too broad classpath directory.
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 0fd0354..3b4647f 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -181,6 +181,11 @@
}
@Override
+ public ProguardTestBuilder addDataEntryResources(DataEntryResource... resources) {
+ throw new Unimplemented("No support for adding data entry resources");
+ }
+
+ @Override
public ProguardTestBuilder addProgramClasses(Collection<Class<?>> classes) {
// Adding a collection of classes will build a jar of exactly those classes so that no other
// classes are made available via a too broad classpath directory.
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index daf5568..e8555d7 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -97,6 +97,11 @@
}
@Override
+ public R8TestBuilder addDataEntryResources(DataEntryResource... resources) {
+ return addDataResources(Arrays.asList(resources));
+ }
+
+ @Override
public R8TestBuilder addKeepRuleFiles(List<Path> files) {
builder.addProguardConfigurationFiles(files);
return self();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 9d8e03c..a17ab09 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -46,6 +46,8 @@
return minification(false);
}
+ public abstract T addDataEntryResources(DataEntryResource... resources);
+
public abstract T addKeepRuleFiles(List<Path> files);
public T addKeepRuleFiles(Path... files) throws IOException {
@@ -62,6 +64,10 @@
return addKeepRules("-keep class ** { *; }");
}
+ public T addKeepAllInterfacesRule() {
+ return addKeepRules("-keep interface ** { *; }");
+ }
+
public T addKeepClassRules(Class<?>... classes) {
for (Class<?> clazz : classes) {
addKeepRules("-keep class " + clazz.getTypeName());
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f53b8e6..ea774b8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1774,6 +1774,7 @@
Executors.newSingleThreadExecutor(),
application,
null,
+ null,
GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null,
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 8d6953b..298ffe1 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -139,6 +139,7 @@
ApplicationWriter writer =
new ApplicationWriter(
application,
+ null,
options,
null,
null,
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index ff948aa..5128e00 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -777,6 +777,7 @@
ApplicationWriter writer =
new ApplicationWriter(
application,
+ null,
options,
null,
null,
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..15599ef 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
@@ -88,6 +101,10 @@
return resources.get(name);
}
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
public int size() {
return resources.size();
}
@@ -175,7 +192,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 +274,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 +296,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
new file mode 100644
index 0000000..29e5bcd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.AppServices;
+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;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderTest extends TestBase {
+
+ private final Backend backend;
+ private final boolean includeWorldGreeter;
+
+ private DataResourceConsumerForTesting dataResourceConsumer;
+
+ @Parameters(name = "Backend: {0}, include WorldGreeter: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(Backend.values(), BooleanUtils.values());
+ }
+
+ public ServiceLoaderTest(Backend backend, boolean includeWorldGreeter) {
+ this.backend = backend;
+ this.includeWorldGreeter = includeWorldGreeter;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = includeWorldGreeter ? "Hello world!" : "Hello";
+
+ List<String> serviceImplementations = Lists.newArrayList(HelloGreeter.class.getTypeName());
+ if (includeWorldGreeter) {
+ serviceImplementations.add(WorldGreeter.class.getTypeName());
+ }
+
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(ServiceLoaderTest.class)
+ .addKeepMainRule(TestClass.class)
+ .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();
+
+ ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+ assertEquals(includeWorldGreeter, greeterSubject.isPresent());
+
+ ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
+ assertThat(helloGreeterSubject, isPresent());
+
+ ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
+ assertEquals(includeWorldGreeter, worldGreeterSubject.isPresent());
+
+ String serviceFileName =
+ includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName();
+ List<String> lines =
+ dataResourceConsumer.get(AppServices.SERVICE_DIRECTORY_NAME + serviceFileName);
+ 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 -whyareyoukeeping works as intended.
+ }
+
+ @Test
+ public void testResourceElimination() throws Exception {
+ String expectedOutput = "Hello world!";
+
+ List<String> serviceImplementations = Lists.newArrayList(HelloGreeter.class.getTypeName());
+ if (includeWorldGreeter) {
+ serviceImplementations.add(WorldGreeter.class.getTypeName());
+ }
+
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(ServiceLoaderTest.class)
+ .addKeepMainRule(OtherTestClass.class)
+ .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(OtherTestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+ assertThat(greeterSubject, not(isPresent()));
+
+ ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
+ assertThat(helloGreeterSubject, not(isPresent()));
+
+ ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
+ assertThat(worldGreeterSubject, not(isPresent()));
+
+ assertTrue(dataResourceConsumer.isEmpty());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ for (Greeter greeter : ServiceLoader.load(Greeter.class)) {
+ System.out.print(greeter.greeting());
+ }
+ }
+ }
+
+ static class OtherTestClass {
+
+ public static void main(String[] args) {
+ System.out.print("Hello world!");
+ }
+ }
+
+ public interface Greeter {
+
+ String greeting();
+ }
+
+ public static class HelloGreeter implements Greeter {
+
+ @Override
+ public String greeting() {
+ return "Hello";
+ }
+ }
+
+ public static class WorldGreeter implements Greeter {
+
+ @Override
+ public String greeting() {
+ return " world!";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index fb61a57..b99cbdc 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -113,6 +113,7 @@
ApplicationWriter writer =
new ApplicationWriter(
dexApp,
+ null,
options,
null,
null,