Merge commit 'b5a44aa689addd3df744f4ebe7666308fb5c0ea2' into dev-release
Change-Id: I28f97b104b6a7d4fd32490d0d428a1ffeae556ab
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2393dab..5e116de 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -284,7 +284,8 @@
// without iterating again the IR. We fall-back to writing one app with rewriting and
// merging it with the other app in rewriteNonDexInputs.
timing.begin("Rewrite non-dex inputs");
- DexApplication app = rewriteNonDexInputs(appView, inputApp, executor, marker, timing);
+ LazyLoadedDexApplication app =
+ rewriteNonDexInputs(appView, inputApp, executor, marker, timing);
timing.end();
appView.rebuildAppInfo(app);
appView.setNamingLens(NamingLens.getIdentityLens());
@@ -404,7 +405,7 @@
"Api reference stubber", () -> new ApiReferenceStubber(appView).run(executorService));
}
- private static DexApplication rewriteNonDexInputs(
+ private static LazyLoadedDexApplication rewriteNonDexInputs(
AppView<AppInfo> appView,
AndroidApp inputApp,
ExecutorService executor,
@@ -440,9 +441,9 @@
builder.getProgramResourceProviders().clear();
builder.addProgramResourceProvider(convertedCfFiles);
AndroidApp newAndroidApp = builder.build();
- DexApplication newApp =
+ LazyLoadedDexApplication newApp =
new ApplicationReader(newAndroidApp, appView.options(), timing).read(executor);
- DexApplication.Builder<?> finalDexApp = newApp.builder();
+ LazyLoadedDexApplication.Builder finalDexApp = newApp.builder();
for (DexProgramClass dexProgramClass : dexProgramClasses) {
finalDexApp.addProgramClass(dexProgramClass);
}
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 7ddbafb..a800696 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -5,9 +5,9 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AssemblyWriter;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexByteCodeWriter;
import com.android.tools.r8.graph.DexByteCodeWriter.OutputStreamProvider;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.CommandLineOrigin;
@@ -351,7 +351,7 @@
throws IOException {
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
- DexApplication application =
+ LazyLoadedDexApplication application =
new ApplicationReader(
AndroidApp.builder()
.addProgramResourceProvider(() -> Collections.singletonList(programResource))
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 50ad4cd..a2d7fef 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -12,10 +12,10 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateAppSubtypingInfo;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
import com.android.tools.r8.shaking.Enqueuer;
@@ -48,7 +48,7 @@
private void run(AndroidApp app, ExecutorService executor, SortingStringConsumer consumer)
throws IOException {
try {
- DexApplication application =
+ LazyLoadedDexApplication application =
new ApplicationReader(app, options, Timing.empty()).read(executor);
traceMainDexForGenerateMainDexList(executor, application)
.forEach(type -> consumer.accept(type.toBinaryName() + ".class", options.reporter));
@@ -62,12 +62,12 @@
throws ExecutionException {
return traceMainDex(
AppView.createForD8MainDexTracing(
- appView.app().toDirect(), appView.appInfo().getMainDexInfo()),
+ appView.app().asLazy().toDirect(), appView.appInfo().getMainDexInfo()),
executor);
}
public MainDexInfo traceMainDexForGenerateMainDexList(
- ExecutorService executor, DexApplication application) throws ExecutionException {
+ ExecutorService executor, LazyLoadedDexApplication application) throws ExecutionException {
return traceMainDex(AppView.createForR8(application.toDirect()), executor);
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 91a94b1..fd20ec5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -296,9 +296,7 @@
options.partialSubCompilationConfiguration != null
? options.partialSubCompilationConfiguration.asR8().getAndClearKeepDeclarations()
: lazyLoaded.getKeepDeclarations();
- timing.begin("To direct app");
- DirectMappedDexApplication application = lazyLoaded.toDirect();
- timing.end();
+ DirectMappedDexApplication application = lazyLoaded.toDirect(timing);
timing.end();
options
.getLibraryDesugaringOptions()
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java
index afbc968..fec7386 100644
--- a/src/main/java/com/android/tools/r8/R8Partial.java
+++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -245,7 +245,10 @@
r8Options.setKeepSpecificationSources(options.getKeepSpecifications());
r8Options.getTestingOptions().enableEmbeddedKeepAnnotations =
options.getTestingOptions().enableEmbeddedKeepAnnotations;
+ r8Options.configurationConsumer = options.configurationConsumer;
r8Options.mapConsumer = options.mapConsumer;
+ r8Options.proguardSeedsConsumer = options.proguardSeedsConsumer;
+ r8Options.usageInformationConsumer = options.usageInformationConsumer;
r8Options.sourceFileProvider = options.sourceFileProvider;
r8Options
.getLibraryDesugaringOptions()
diff --git a/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ReflectiveEvent.java b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ReflectiveEvent.java
index dc49336..26090d0 100644
--- a/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ReflectiveEvent.java
+++ b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ReflectiveEvent.java
@@ -52,6 +52,14 @@
return null;
}
+ public boolean isServiceLoaderLoad() {
+ return false;
+ }
+
+ public ServiceLoaderLoad asServiceLoaderLoad() {
+ return null;
+ }
+
public boolean isClassGetMember() {
return false;
}
@@ -119,7 +127,7 @@
case ATOMIC_FIELD_UPDATER_NEW_UPDATER:
return new AtomicFieldUpdaterNewUpdater(eventType, stack, args, factory);
case SERVICE_LOADER_LOAD:
- break;
+ return new ServiceLoaderLoad(eventType, stack, args, factory);
case PROXY_NEW_PROXY_INSTANCE:
break;
}
diff --git a/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ServiceLoaderLoad.java b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ServiceLoaderLoad.java
new file mode 100644
index 0000000..782805d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/assistant/postprocessing/model/ServiceLoaderLoad.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2025, 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.assistant.postprocessing.model;
+
+import com.android.tools.r8.assistant.runtime.ReflectiveEventType;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.KeepInfoCollectionExported;
+
+public class ServiceLoaderLoad extends ReflectiveEvent {
+
+ private final DexType type;
+ private final DexType classLoader;
+
+ public ServiceLoaderLoad(
+ ReflectiveEventType eventType, String[] stack, String[] args, DexItemFactory factory) {
+ super(eventType, stack);
+ assert args.length == 2;
+ type = toType(args[0], factory);
+ classLoader = toTypeOrTripleStar(args[0], factory);
+ }
+
+ @Override
+ public boolean isServiceLoaderLoad() {
+ return true;
+ }
+
+ @Override
+ public ServiceLoaderLoad asServiceLoaderLoad() {
+ return this;
+ }
+
+ public DexType getType() {
+ return type;
+ }
+
+ @Override
+ public String getContentsString() {
+ return type + ", " + classLoader;
+ }
+
+ @Override
+ public boolean isKeptBy(KeepInfoCollectionExported keepInfoCollectionExported) {
+ return keepInfoCollectionExported.getKeepClassInfo(type.asTypeReference()) != null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index bd1bc7a..f361d81 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -268,7 +268,7 @@
}
private void readProguardMap(
- StringResource map, DexApplication.Builder<?> builder, TaskCollection<?> tasks)
+ StringResource map, DexApplication.Builder<?, ?> builder, TaskCollection<?> tasks)
throws ExecutionException {
// Read the Proguard mapping file in parallel with DexCode and DexProgramClass items.
if (map == null) {
diff --git a/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java
index d9f6def..9f0fa1f 100644
--- a/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/DefaultMixedSectionLayoutStrategy.java
@@ -78,13 +78,12 @@
this.appView = appView;
}
- public void addCode(DexWritableCode dexWritableCode, ProgramMethod method) {
+ public void addCode(DexWritableCode code, ProgramMethod method) {
assert appView.options().canUseCanonicalizedCodeObjects();
if (counts == null) {
counts = new HashMap<>();
}
- DexWritableCacheKey cacheKey =
- dexWritableCode.getCacheLookupKey(method, appView.dexItemFactory());
+ DexWritableCacheKey cacheKey = code.getCacheLookupKey(method, appView.dexItemFactory());
if (!counts.containsKey(cacheKey)) {
counts.put(cacheKey, 1);
} else {
@@ -98,9 +97,11 @@
|| method.getDefinition().getDexWritableCodeOrNull() == null;
return 1;
}
- DexWritableCode dexWritableCodeOrNull = method.getDefinition().getDexWritableCodeOrNull();
- DexWritableCacheKey cacheLookupKey =
- dexWritableCodeOrNull.getCacheLookupKey(method, appView.dexItemFactory());
+ DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
+ if (!code.canBeCanonicalized(appView.options())) {
+ return 1;
+ }
+ DexWritableCacheKey cacheLookupKey = code.getCacheLookupKey(method, appView.dexItemFactory());
assert counts.containsKey(cacheLookupKey);
return counts.get(cacheLookupKey);
}
@@ -117,7 +118,7 @@
DexWritableCode code = method.getDefinition().getDexWritableCodeOrNull();
assert code != null || method.getDefinition().shouldNotHaveCode();
if (code != null) {
- if (appView.options().canUseCanonicalizedCodeObjects()) {
+ if (code.canBeCanonicalized(appView.options())) {
codeCounts.addCode(code, method);
}
codesSorted.add(method);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 7cdd5cc..5826972 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -251,9 +251,7 @@
Map<DexWritableCacheKey, Integer> offsetCache = new HashMap<>();
for (ProgramMethod method : codes) {
DexWritableCode dexWritableCode = method.getDefinition().getCode().asDexWritableCode();
- if (!options.canUseCanonicalizedCodeObjects()) {
- writeCodeItem(method, dexWritableCode);
- } else {
+ if (dexWritableCode.canBeCanonicalized(options)) {
DexWritableCacheKey cacheLookupKey =
dexWritableCode.getCacheLookupKey(method, appView.dexItemFactory());
Integer offsetOrNull = offsetCache.get(cacheLookupKey);
@@ -262,6 +260,8 @@
} else {
offsetCache.put(cacheLookupKey, writeCodeItem(method, dexWritableCode));
}
+ } else {
+ writeCodeItem(method, dexWritableCode);
}
}
assert sizeAndCountOfCodeItems.getCount()
@@ -479,7 +479,7 @@
Set<DexWritableCacheKey> cache = new HashSet<>();
for (ProgramMethod method : methods) {
DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
- if (!options.canUseCanonicalizedCodeObjects()
+ if (!code.canBeCanonicalized(options)
|| cache.add(code.getCacheLookupKey(method, appView.dexItemFactory()))) {
sizeAndCount.count++;
sizeAndCount.size = alignSize(4, sizeAndCount.size) + sizeOfCodeItem(code);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
index 70079d8..faa31b9 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
@@ -229,6 +229,14 @@
return null;
}
+ public boolean isInvokeSuper() {
+ return false;
+ }
+
+ public boolean isInvokeSuperRange() {
+ return false;
+ }
+
public boolean isInvokeVirtual() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeSuper.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeSuper.java
index 46af3323229..1586793 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeSuper.java
@@ -49,6 +49,11 @@
}
@Override
+ public boolean isInvokeSuper() {
+ return true;
+ }
+
+ @Override
public void registerUse(UseRegistry<?> registry) {
registry.registerInvokeSuper(getMethod());
}
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeSuperRange.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeSuperRange.java
index 015f6a5..2a8e5a0 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeSuperRange.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeSuperRange.java
@@ -49,6 +49,11 @@
}
@Override
+ public boolean isInvokeSuperRange() {
+ return true;
+ }
+
+ @Override
public void registerUse(UseRegistry<?> registry) {
registry.registerInvokeSuper(getMethod());
}
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 40c2ff0..38264ec 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -43,7 +43,7 @@
private final RetracerForCodePrinting retracer;
public AssemblyWriter(
- DexApplication application,
+ LazyLoadedDexApplication application,
InternalOptions options,
boolean allInfo,
boolean writeIR,
@@ -52,7 +52,7 @@
}
public AssemblyWriter(
- DexApplication application,
+ LazyLoadedDexApplication application,
InternalOptions options,
boolean allInfo,
boolean writeIR,
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 2c41285..a74e532 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -7,7 +7,6 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.DataResourceProvider;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.timing.Timing;
@@ -47,7 +46,7 @@
this.timing = timing;
}
- public abstract Builder<?> builder();
+ public abstract Builder<?, ?> builder();
@Override
public DexItemFactory dexItemFactory() {
@@ -142,7 +141,7 @@
return proguardMap;
}
- public abstract static class Builder<T extends Builder<T>> {
+ public abstract static class Builder<S extends DexApplication, T extends Builder<S, T>> {
private final List<DexProgramClass> programClasses = new ArrayList<>();
@@ -163,7 +162,7 @@
abstract T self();
public Builder(DexApplication application) {
- flags = application.flags;
+ flags = application.getFlags();
programClasses.addAll(application.programClasses());
dataResourceProviders.addAll(application.dataResourceProviders);
proguardMap = application.getProguardMap();
@@ -217,7 +216,6 @@
}
});
this.flags = builder.build();
-
return self();
}
@@ -244,7 +242,11 @@
return programClasses;
}
- public abstract DexApplication build();
+ public final S build() {
+ return build(Timing.empty());
+ }
+
+ public abstract S build(Timing timing);
}
public static LazyLoadedDexApplication.Builder builder(InternalOptions options, Timing timing) {
@@ -252,11 +254,14 @@
}
public DirectMappedDexApplication asDirect() {
- throw new Unreachable("Cannot use a LazyDexApplication where a DirectDexApplication is"
- + " expected.");
+ return null;
}
- public abstract DirectMappedDexApplication toDirect();
+ public boolean isDirect() {
+ return false;
+ }
- public abstract boolean isDirect();
+ public LazyLoadedDexApplication asLazy() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index e64405a..1150791 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.lightir.ByteUtils;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.DexDebugUtils.PositionInfo;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.RetracerForCodePrinting;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.structural.Equatable;
@@ -159,6 +160,14 @@
int unused = hashCode(); // Cache the hash code eagerly.
}
+ @Override
+ public boolean canBeCanonicalized(InternalOptions options) {
+ // Do not canonicalize code objects with invoke-super instructions due to ART's thread
+ // interpreter cache. See also b/445349082.
+ return options.canUseCanonicalizedCodeObjects()
+ && ArrayUtils.none(instructions, i -> i.isInvokeSuper() || i.isInvokeSuperRange());
+ }
+
public DexCode withCodeLens(GraphLens codeLens) {
return new DexCode(this) {
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 ad567ab..ad19a9f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -53,7 +53,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -2943,9 +2942,15 @@
public class ProxyMethods {
+ public final DexMethod getProxyClass;
public final DexMethod newProxyInstance;
private ProxyMethods() {
+ getProxyClass =
+ createMethod(
+ proxyType,
+ createProto(classType, classLoaderType, classArrayType),
+ createString("getProxyClass"));
newProxyInstance =
createMethod(
proxyType,
@@ -3668,37 +3673,6 @@
allTypes.forEach(f);
}
- public void gc() {
- markers = new WeakHashMap<>(markers);
- methodHandles = new WeakHashMap<>(methodHandles);
- strings = new WeakHashMap<>(strings);
- types = new WeakHashMap<>(types);
- fields = new WeakHashMap<>(fields);
- protos = new WeakHashMap<>(protos);
- methods = new WeakHashMap<>(methods);
- committedMethodHandles = new WeakHashMap<>(committedMethodHandles);
- committedStrings = new WeakHashMap<>(committedStrings);
- committedTypes = new WeakHashMap<>(committedTypes);
- committedFields = new WeakHashMap<>(committedFields);
- committedProtos = new WeakHashMap<>(committedProtos);
- committedMethods = new WeakHashMap<>(committedMethods);
- System.gc();
- System.gc();
- markers = new ConcurrentHashMap<>(markers);
- methodHandles = new ConcurrentHashMap<>(methodHandles);
- strings = new ConcurrentHashMap<>(strings);
- types = new ConcurrentHashMap<>(types);
- fields = new ConcurrentHashMap<>(fields);
- protos = new ConcurrentHashMap<>(protos);
- methods = new ConcurrentHashMap<>(methods);
- committedMethodHandles = new HashMap<>(committedMethodHandles);
- committedStrings = new HashMap<>(committedStrings);
- committedTypes = new HashMap<>(committedTypes);
- committedFields = new HashMap<>(committedFields);
- committedProtos = new HashMap<>(committedProtos);
- committedMethods = new HashMap<>(committedMethods);
- }
-
public void commitPendingItems() {
commitPendingItems(committedMethodHandles, methodHandles);
commitPendingItems(committedStrings, strings);
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
index e5037e5..7f018ce 100644
--- a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexCode.TryHandler;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.HashingVisitor;
import java.nio.ShortBuffer;
@@ -24,6 +25,10 @@
THROW_EXCEPTION
}
+ default boolean canBeCanonicalized(InternalOptions options) {
+ return options.canUseCanonicalizedCodeObjects();
+ }
+
boolean isThrowExceptionCode();
ThrowExceptionCode asThrowExceptionCode();
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 3f654fa..702bd04 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -131,11 +131,6 @@
}
@Override
- public DirectMappedDexApplication toDirect() {
- return this;
- }
-
- @Override
public boolean isDirect() {
return true;
}
@@ -211,7 +206,7 @@
return true;
}
- public static class Builder extends DexApplication.Builder<Builder> {
+ public static class Builder extends DexApplication.Builder<DirectMappedDexApplication, Builder> {
private ImmutableCollection<DexClasspathClass> classpathClasses;
private Map<DexType, DexLibraryClass> libraryClasses;
@@ -219,10 +214,8 @@
private final List<DexClasspathClass> pendingClasspathClasses = new ArrayList<>();
private final Set<DexType> pendingClasspathRemovalIfPresent = Sets.newIdentityHashSet();
- Builder(LazyLoadedDexApplication application) {
+ Builder(LazyLoadedDexApplication application, AllClasses allClasses) {
super(application);
- // As a side-effect, this will force-load all classes.
- AllClasses allClasses = application.loadAllClasses();
classpathClasses = allClasses.getClasspathClasses().values();
libraryClasses = allClasses.getLibraryClasses();
replaceProgramClasses(allClasses.getProgramClasses().values());
@@ -325,36 +318,39 @@
}
@Override
- public DirectMappedDexApplication build() {
- // Rebuild the map. This will fail if keys are not unique.
- // TODO(zerny): Consider not rebuilding the map if no program classes are added.
- commitPendingClasspathClasses();
- Map<DexType, ProgramOrClasspathClass> programAndClasspathClasses =
- new IdentityHashMap<>(getProgramClasses().size() + classpathClasses.size());
- // Note: writing classes in reverse priority order, so a duplicate will be correctly ordered.
- // There should not be duplicates between program and classpath and that is asserted in the
- // addAll subroutine.
- ImmutableCollection<DexClasspathClass> newClasspathClasses = classpathClasses;
- if (addAll(programAndClasspathClasses, classpathClasses)) {
- ImmutableList.Builder<DexClasspathClass> builder = ImmutableList.builder();
- for (DexClasspathClass classpathClass : classpathClasses) {
- if (!pendingClasspathRemovalIfPresent.contains(classpathClass.getType())) {
- builder.add(classpathClass);
+ public DirectMappedDexApplication build(Timing timing) {
+ try (Timing t0 = timing.begin("Build")) {
+ // Rebuild the map. This will fail if keys are not unique.
+ // TODO(zerny): Consider not rebuilding the map if no program classes are added.
+ commitPendingClasspathClasses();
+ Map<DexType, ProgramOrClasspathClass> programAndClasspathClasses =
+ new IdentityHashMap<>(getProgramClasses().size() + classpathClasses.size());
+ // Note: writing classes in reverse priority order, so a duplicate will be correctly
+ // ordered.
+ // There should not be duplicates between program and classpath and that is asserted in the
+ // addAll subroutine.
+ ImmutableCollection<DexClasspathClass> newClasspathClasses = classpathClasses;
+ if (addAll(programAndClasspathClasses, classpathClasses)) {
+ ImmutableList.Builder<DexClasspathClass> builder = ImmutableList.builder();
+ for (DexClasspathClass classpathClass : classpathClasses) {
+ if (!pendingClasspathRemovalIfPresent.contains(classpathClass.getType())) {
+ builder.add(classpathClass);
+ }
}
+ newClasspathClasses = builder.build();
}
- newClasspathClasses = builder.build();
+ addAll(programAndClasspathClasses, getProgramClasses());
+ return new DirectMappedDexApplication(
+ proguardMap,
+ flags,
+ ImmutableMap.copyOf(programAndClasspathClasses),
+ getLibraryClassesAsImmutableMap(),
+ ImmutableList.copyOf(getProgramClasses()),
+ newClasspathClasses,
+ ImmutableList.copyOf(dataResourceProviders),
+ options,
+ timing);
}
- addAll(programAndClasspathClasses, getProgramClasses());
- return new DirectMappedDexApplication(
- proguardMap,
- flags,
- ImmutableMap.copyOf(programAndClasspathClasses),
- getLibraryClassesAsImmutableMap(),
- ImmutableList.copyOf(getProgramClasses()),
- newClasspathClasses,
- ImmutableList.copyOf(dataResourceProviders),
- options,
- timing);
}
private <T extends ProgramOrClasspathClass> boolean addAll(
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index a4cc5a7..447178a 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -149,74 +149,82 @@
private final ImmutableMap<DexType, DexClasspathClass> classpathClasses;
private final ImmutableMap<DexType, DexLibraryClass> libraryClasses;
- @SuppressWarnings("ReferenceEquality")
AllClasses(
LibraryClassCollection libraryClassesLoader,
ClasspathClassCollection classpathClassesLoader,
Map<DexType, DexClasspathClass> synthesizedClasspathClasses,
ProgramClassCollection programClassesLoader,
- InternalOptions options) {
-
+ InternalOptions options,
+ Timing timing) {
// When desugaring VarHandle do not read the VarHandle and MethodHandles$Lookup classes
// from the library as they will be synthesized during desugaring.
+ DexItemFactory factory = options.dexItemFactory();
Predicate<DexType> forceLoadPredicate =
type ->
!(options.shouldDesugarVarHandle()
- && (type == options.dexItemFactory().varHandleType
- || type == options.dexItemFactory().lookupType));
+ && (type.isIdenticalTo(factory.varHandleType)
+ || type.isIdenticalTo(factory.lookupType)));
// Force-load library classes.
ImmutableMap<DexType, DexLibraryClass> allLibraryClasses;
- if (libraryClassesLoader != null) {
- libraryClassesLoader.forceLoad(forceLoadPredicate);
- allLibraryClasses = libraryClassesLoader.getAllClassesInMap();
- } else {
- allLibraryClasses = ImmutableMap.of();
+ try (Timing t0 = timing.begin("Force-load library classes")) {
+ if (libraryClassesLoader != null) {
+ libraryClassesLoader.forceLoad(forceLoadPredicate);
+ allLibraryClasses = libraryClassesLoader.getAllClassesInMap();
+ } else {
+ allLibraryClasses = ImmutableMap.of();
+ }
}
// Program classes should be fully loaded.
- assert programClassesLoader != null;
- assert programClassesLoader.isFullyLoaded();
- ImmutableMap<DexType, DexProgramClass> allProgramClasses =
- programClassesLoader.forceLoad().getAllClassesInMap();
+ ImmutableMap<DexType, DexProgramClass> allProgramClasses;
+ try (Timing t0 = timing.begin("Force-load program classes")) {
+ assert programClassesLoader != null;
+ assert programClassesLoader.isFullyLoaded();
+ allProgramClasses = programClassesLoader.forceLoad().getAllClassesInMap();
+ }
// Force-load classpath classes.
- ImmutableMap.Builder<DexType, DexClasspathClass> allClasspathClassesBuilder =
- ImmutableMap.builder();
- if (classpathClassesLoader != null) {
- classpathClassesLoader.forceLoad().forEach(allClasspathClassesBuilder::put);
+ ImmutableMap<DexType, DexClasspathClass> allClasspathClasses;
+ try (Timing t0 = timing.begin("Force-load classpath classes")) {
+ ImmutableMap.Builder<DexType, DexClasspathClass> allClasspathClassesBuilder =
+ ImmutableMap.builder();
+ if (classpathClassesLoader != null) {
+ classpathClassesLoader.forceLoad().forEach(allClasspathClassesBuilder::put);
+ }
+ if (synthesizedClasspathClasses != null) {
+ allClasspathClassesBuilder.putAll(synthesizedClasspathClasses);
+ }
+ allClasspathClasses = allClasspathClassesBuilder.build();
}
- if (synthesizedClasspathClasses != null) {
- allClasspathClassesBuilder.putAll(synthesizedClasspathClasses);
- }
- ImmutableMap<DexType, DexClasspathClass> allClasspathClasses =
- allClasspathClassesBuilder.build();
// Collect loaded classes in the precedence order library classes, program classes and
// class path classes or program classes, classpath classes and library classes depending
// on the configured lookup order.
- if (options.loadAllClassDefinitions) {
- libraryClasses = allLibraryClasses;
- programClasses = allProgramClasses;
- classpathClasses =
- fillPrioritizedClasses(allClasspathClasses, programClasses::get, options);
- } else {
- programClasses = fillPrioritizedClasses(allProgramClasses, type -> null, options);
- classpathClasses =
- fillPrioritizedClasses(allClasspathClasses, programClasses::get, options);
- libraryClasses =
- fillPrioritizedClasses(
- allLibraryClasses,
- type -> {
- DexProgramClass clazz = programClasses.get(type);
- if (clazz != null) {
- options.recordLibraryAndProgramDuplicate(
- type, clazz, allLibraryClasses.get(type));
- return clazz;
- }
- return classpathClasses.get(type);
- },
- options);
+ try (Timing t0 = timing.begin("Fill prioritized classes")) {
+ if (options.loadAllClassDefinitions) {
+ libraryClasses = allLibraryClasses;
+ programClasses = allProgramClasses;
+ classpathClasses =
+ fillPrioritizedClasses(allClasspathClasses, programClasses::get, options);
+ } else {
+ programClasses = fillPrioritizedClasses(allProgramClasses, type -> null, options);
+ classpathClasses =
+ fillPrioritizedClasses(allClasspathClasses, programClasses::get, options);
+ libraryClasses =
+ fillPrioritizedClasses(
+ allLibraryClasses,
+ type -> {
+ DexProgramClass clazz = programClasses.get(type);
+ if (clazz != null) {
+ options.recordLibraryAndProgramDuplicate(
+ type, clazz, allLibraryClasses.get(type));
+ return clazz;
+ }
+ return classpathClasses.get(type);
+ },
+ options);
+ }
}
}
@@ -277,15 +285,18 @@
options.reporter.warning(message);
}
- /**
- * Force load all classes and return type -> class map containing all the classes.
- */
- public AllClasses loadAllClasses() {
+ /** Force load all classes and return type -> class map containing all the classes. */
+ public AllClasses loadAllClasses(Timing timing) {
return new AllClasses(
- libraryClasses, classpathClasses, synthesizedClasspathClasses, programClasses, options);
+ libraryClasses,
+ classpathClasses,
+ synthesizedClasspathClasses,
+ programClasses,
+ options,
+ timing);
}
- public static class Builder extends DexApplication.Builder<Builder> {
+ public static class Builder extends DexApplication.Builder<LazyLoadedDexApplication, Builder> {
private ClasspathClassCollection classpathClasses;
private Map<DexType, DexClasspathClass> synthesizedClasspathClasses;
@@ -345,7 +356,7 @@
}
@Override
- public LazyLoadedDexApplication build() {
+ public LazyLoadedDexApplication build(Timing timing) {
ProgramClassConflictResolver resolver =
options.programClassConflictResolver == null
? ProgramClassCollection.defaultConflictResolver(options.reporter)
@@ -370,13 +381,22 @@
}
@Override
- public DirectMappedDexApplication toDirect() {
- return new DirectMappedDexApplication.Builder(this).build().asDirect();
+ public LazyLoadedDexApplication asLazy() {
+ return this;
}
- @Override
- public boolean isDirect() {
- return false;
+ public DirectMappedDexApplication toDirect() {
+ return toDirect(Timing.empty());
+ }
+
+ public DirectMappedDexApplication toDirect(Timing timing) {
+ try (Timing t0 = timing.begin("To direct app")) {
+ // As a side-effect, this will force-load all classes.
+ AllClasses allClasses = loadAllClasses(timing);
+ DirectMappedDexApplication.Builder builder =
+ new DirectMappedDexApplication.Builder(this, allClasses);
+ return builder.build(timing);
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
index f431563..20cc228 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
@@ -84,7 +83,7 @@
processCovariantReturnTypeAnnotations(profileCollectionAdditions, executorService);
// Build a new application with jumbo string info,
- Builder<?> builder = application.builder();
+ DexApplication.Builder<?, ?> builder = application.builder();
if (appView.options().getLibraryDesugaringOptions().isDesugaredLibraryCompilation()) {
new EmulatedInterfaceApplicationRewriter(appView).rewriteApplication(builder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
index 35d8ff3..bc3be29 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
@@ -503,7 +504,7 @@
ExecutorService executorService = ThreadUtils.getExecutorService(options);
assert !options.ignoreJavaLibraryOverride;
options.ignoreJavaLibraryOverride = true;
- DexApplication appForMax = applicationReader.read(executorService);
+ LazyLoadedDexApplication appForMax = applicationReader.read(executorService);
options.ignoreJavaLibraryOverride = false;
DexClass varHandle =
appForMax.definitionFor(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
index 27265e4..2db425e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -38,7 +38,7 @@
.forEach((ei, descriptor) -> emulatedInterfaces.put(ei, descriptor.getRewrittenType()));
}
- public void rewriteApplication(DexApplication.Builder<?> builder) {
+ public void rewriteApplication(DexApplication.Builder<?, ?> builder) {
assert appView.options().getLibraryDesugaringOptions().isDesugaredLibraryCompilation();
ArrayList<DexProgramClass> newProgramClasses = new ArrayList<>();
for (DexProgramClass clazz : builder.getProgramClasses()) {
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
index 5906b9f..61362ac 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
@@ -184,7 +184,7 @@
desugaredOutputClasses.add(clazz);
}
}
- DirectMappedDexApplication app = appView.app().toDirect();
+ DirectMappedDexApplication app = appView.app().asLazy().toDirect();
outputClasspathClasses = app.classpathClasses();
outputLibraryClasses = app.libraryClasses();
startupProfile = appView.getStartupProfile();
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 0b201a2..7f2238d 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -228,7 +228,9 @@
boolean classNameResult = memberRule.getClassNames().matches(source.type);
assert classNameResult;
if (memberRule.hasInheritanceClassName()) {
- boolean inheritanceResult = rootSetBuilder.satisfyInheritanceRule(target, memberRule);
+ boolean inheritanceResult =
+ memberRule.satisfyInheritanceRule(
+ target, appView, rootSetBuilder::handleMatchedAnnotation);
assert inheritanceResult;
}
}
@@ -278,7 +280,7 @@
}
if (rule.hasInheritanceClassName()) {
// Try another live type since the current one doesn't satisfy the inheritance rule.
- return rootSetBuilder.satisfyInheritanceRule(clazz, rule);
+ return rule.satisfyInheritanceRule(clazz, appView, rootSetBuilder::handleMatchedAnnotation);
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
index 8685347..a3b9e8c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
@@ -79,6 +79,16 @@
}
@Override
+ public boolean isApplicableToClasspathClasses() {
+ return true;
+ }
+
+ @Override
+ public boolean isApplicableToLibraryClasses() {
+ return true;
+ }
+
+ @Override
String typeString() {
return "assumevalues";
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index 9555058..fd68364 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -38,6 +38,8 @@
return new SingleClassNameList(matcher);
}
+ public abstract boolean isMatchAnyClassPattern();
+
public abstract int size();
public static class Builder {
@@ -154,6 +156,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return false;
+ }
+
+ @Override
public int size() {
return 0;
}
@@ -207,6 +214,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return className.isMatchAnyClassPattern();
+ }
+
+ @Override
public int size() {
return 1;
}
@@ -300,6 +312,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return false;
+ }
+
+ @Override
public int size() {
return classNames.size();
}
@@ -412,6 +429,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return false;
+ }
+
+ @Override
public int size() {
return classNames.size();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 98f41cd..00e4830 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -264,6 +265,19 @@
return memberRules;
}
+ public void getFieldAndMethodRules(
+ Collection<ProguardMemberRule> fieldRules, Collection<ProguardMemberRule> methodRules) {
+ for (ProguardMemberRule memberRule : memberRules) {
+ ProguardMemberType ruleType = memberRule.getRuleType();
+ if (ruleType.includesFields()) {
+ fieldRules.add(memberRule);
+ }
+ if (ruleType.includesMethods()) {
+ methodRules.add(memberRule);
+ }
+ }
+ }
+
public boolean getInheritanceIsExtends() {
return inheritanceIsExtends;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 828ca04..84518dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.shaking;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder.satisfyAccessFlag;
+import static com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder.satisfyAnnotation;
+import static com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder.satisfyClassType;
+import static com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder.satisfyNonSyntheticClass;
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -15,8 +19,9 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
-import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Iterables;
import java.util.List;
@@ -30,6 +35,7 @@
// TODO(b/164019179): Since we are using the rule language for tracing main dex we can end up in
// a situation where the references to types are dead.
private boolean canReferenceDeadTypes = false;
+ private OptionalBool trivialAllClassMatch = OptionalBool.unknown();
ProguardConfigurationRule(
Origin origin,
@@ -61,21 +67,27 @@
memberRules);
}
- public boolean isTrivalAllClassMatch() {
- BooleanBox booleanBox = new BooleanBox(true);
- getClassNames()
- .forEachTypeMatcher(
- unused -> booleanBox.set(false),
- proguardTypeMatcher -> !proguardTypeMatcher.isMatchAnyClassPattern());
- return booleanBox.get()
+ public boolean isTrivialAllClassMatch() {
+ if (trivialAllClassMatch.isUnknown()) {
+ trivialAllClassMatch = OptionalBool.of(computeIsTrivialAllClassMatch());
+ }
+ assert trivialAllClassMatch.isTrue() == computeIsTrivialAllClassMatch();
+ return trivialAllClassMatch.isTrue();
+ }
+
+ public boolean isTrivialAllClassMatchWithNoMembersRules() {
+ return isTrivialAllClassMatch() && !hasMemberRules();
+ }
+
+ public boolean computeIsTrivialAllClassMatch() {
+ return getClassNames().isMatchAnyClassPattern()
&& getClassAnnotations().isEmpty()
&& getClassAccessFlags().isDefaultFlags()
&& getNegatedClassAccessFlags().isDefaultFlags()
&& !getClassTypeNegated()
&& getClassType() == ProguardClassType.CLASS
&& getInheritanceAnnotations().isEmpty()
- && getInheritanceClassName() == null
- && getMemberRules().isEmpty();
+ && !hasInheritanceClassName();
}
public boolean isUsed() {
@@ -201,6 +213,10 @@
return false;
}
+ public final boolean isOnlyApplicableToProgramClasses() {
+ return !isApplicableToClasspathClasses() && !isApplicableToLibraryClasses();
+ }
+
protected boolean hasBackReferences() {
return !Iterables.isEmpty(getBackReferences());
}
@@ -263,4 +279,141 @@
super.append(builder);
return builder;
}
+
+ public boolean testClassCondition(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ if (!satisfyNonSyntheticClass(clazz, appView)) {
+ return false;
+ }
+ if (!satisfyClassType(this, clazz)) {
+ return false;
+ }
+ if (!satisfyAccessFlag(this, clazz)) {
+ return false;
+ }
+ AnnotationMatchResult annotationMatchResult = satisfyAnnotation(this, clazz);
+ if (annotationMatchResult == null) {
+ return false;
+ }
+ annotationMatchResultConsumer.accept(annotationMatchResult);
+ // In principle it should make a difference whether the user specified in a class
+ // spec that a class either extends or implements another type. However, proguard
+ // seems not to care, so users have started to use this inconsistently. We are thus
+ // inconsistent, as well, but tell them.
+ // TODO(herhut): One day make this do what it says.
+ if (hasInheritanceClassName()
+ && !satisfyInheritanceRule(clazz, appView, annotationMatchResultConsumer)) {
+ return false;
+ }
+ return getClassNames().matches(clazz.type);
+ }
+
+ public boolean satisfyInheritanceRule(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ return satisfyExtendsRule(clazz, appView, annotationMatchResultConsumer)
+ || satisfyImplementsRule(clazz, appView, annotationMatchResultConsumer);
+ }
+
+ private boolean satisfyExtendsRule(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ if (anySuperTypeMatchesExtendsRule(clazz.superType, appView, annotationMatchResultConsumer)) {
+ return true;
+ }
+ // It is possible that this class used to inherit from another class X, but no longer does it,
+ // because X has been merged into `clazz`.
+ return anySourceMatchesInheritanceRuleDirectly(clazz, false, appView);
+ }
+
+ private boolean anySuperTypeMatchesExtendsRule(
+ DexType type,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ while (type != null) {
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `class`.
+ if (getInheritanceClassName().matches(clazz.type, appView)) {
+ AnnotationMatchResult annotationMatchResult =
+ RootSetBuilder.containsAllAnnotations(getInheritanceAnnotations(), clazz);
+ if (annotationMatchResult != null) {
+ annotationMatchResultConsumer.accept(annotationMatchResult);
+ return true;
+ }
+ }
+ type = clazz.superType;
+ }
+ return false;
+ }
+
+ private boolean satisfyImplementsRule(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ if (anyImplementedInterfaceMatchesImplementsRule(
+ clazz, appView, annotationMatchResultConsumer)) {
+ return true;
+ }
+ // It is possible that this class used to implement an interface I, but no longer does it,
+ // because I has been merged into `clazz`.
+ return anySourceMatchesInheritanceRuleDirectly(clazz, true, appView);
+ }
+
+ private boolean anyImplementedInterfaceMatchesImplementsRule(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ // TODO(herhut): Maybe it would be better to do this breadth first.
+ for (DexType iface : clazz.getInterfaces()) {
+ DexClass ifaceClass = appView.definitionFor(iface);
+ if (ifaceClass == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ continue;
+ }
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `ifaceClass`.
+ if (getInheritanceClassName().matches(iface, appView)) {
+ AnnotationMatchResult annotationMatchResult =
+ RootSetBuilder.containsAllAnnotations(getInheritanceAnnotations(), ifaceClass);
+ if (annotationMatchResult != null) {
+ annotationMatchResultConsumer.accept(annotationMatchResult);
+ return true;
+ }
+ }
+ if (anyImplementedInterfaceMatchesImplementsRule(
+ ifaceClass, appView, annotationMatchResultConsumer)) {
+ return true;
+ }
+ }
+ if (!clazz.hasSuperType()) {
+ return false;
+ }
+ DexClass superClass = appView.definitionFor(clazz.getSuperType());
+ return superClass != null
+ && anyImplementedInterfaceMatchesImplementsRule(
+ superClass, appView, annotationMatchResultConsumer);
+ }
+
+ private boolean anySourceMatchesInheritanceRuleDirectly(
+ DexClass clazz, boolean isInterface, AppView<?> appView) {
+ // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
+ // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
+ return appView.getVerticallyMergedClasses() != null
+ && appView.getVerticallyMergedClasses().getSourcesFor(clazz.type).stream()
+ .filter(
+ sourceType ->
+ appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
+ .anyMatch(getInheritanceClassName()::matches);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
index 81919d3..000cfc3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
@@ -33,7 +33,7 @@
public void disallowOptimizationsForReevaluation(
Enqueuer enqueuer, ConsequentRootSetBuilder rootSetBuilder) {
if (enqueuer.getMode().isInitialTreeShaking()
- && !ifRule.isTrivalAllClassMatch()
+ && !ifRule.isTrivialAllClassMatchWithNoMembersRules()
&& classMatch.isProgramClass()) {
disallowClassOptimizationsForReevaluation(rootSetBuilder);
disallowMethodOptimizationsForReevaluation(rootSetBuilder);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 13653e1..2541fcb 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -339,39 +339,29 @@
return this;
}
+ private void process(
+ Collection<DexClass> classes,
+ ProguardConfigurationRule rule,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ for (DexClass clazz : classes) {
+ process(clazz, rule, ifRulePreconditionMatch, timing);
+ }
+ }
+
// Process a class with the keep rule.
private void process(
DexClass clazz,
ProguardConfigurationRule rule,
- ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
- if (!satisfyNonSyntheticClass(clazz, appView)) {
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ if (!rule.testClassCondition(clazz, appView, this::handleMatchedAnnotation)) {
return;
}
- if (!satisfyClassType(rule, clazz)) {
- return;
- }
- if (!satisfyAccessFlag(rule, clazz)) {
- return;
- }
- AnnotationMatchResult annotationMatchResult = satisfyAnnotation(rule, clazz);
- if (annotationMatchResult == null) {
- return;
- }
- handleMatchedAnnotation(annotationMatchResult);
- // In principle it should make a difference whether the user specified in a class
- // spec that a class either extends or implements another type. However, proguard
- // seems not to care, so users have started to use this inconsistently. We are thus
- // inconsistent, as well, but tell them.
- // TODO(herhut): One day make this do what it says.
- if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) {
- return;
- }
-
- if (!rule.getClassNames().matches(clazz.type)) {
- return;
- }
-
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
+ Collection<ProguardMemberRule> fieldKeepRules = new ArrayList<>();
+ Collection<ProguardMemberRule> methodKeepRules = new ArrayList<>();
+ rule.getFieldAndMethodRules(fieldKeepRules, methodKeepRules);
Map<Predicate<DexDefinition>, DexClass> preconditionSupplier;
if (rule instanceof ProguardKeepRule) {
ProguardKeepRule keepRule = rule.asProguardKeepRule();
@@ -389,7 +379,7 @@
preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
markMatchingVisibleMethods(
clazz,
- memberKeepRules,
+ methodKeepRules,
rule,
preconditionSupplier,
keepRule.getIncludeDescriptorClasses(),
@@ -397,7 +387,7 @@
ifRulePreconditionMatch);
markMatchingVisibleFields(
clazz,
- memberKeepRules,
+ fieldKeepRules,
rule,
preconditionSupplier,
keepRule.getIncludeDescriptorClasses(),
@@ -424,7 +414,7 @@
}
markMatchingVisibleMethods(
clazz,
- memberKeepRules,
+ methodKeepRules,
rule,
preconditionSupplier,
keepRule.getIncludeDescriptorClasses(),
@@ -432,7 +422,7 @@
ifRulePreconditionMatch);
markMatchingVisibleFields(
clazz,
- memberKeepRules,
+ fieldKeepRules,
rule,
preconditionSupplier,
keepRule.getIncludeDescriptorClasses(),
@@ -458,29 +448,29 @@
|| rule instanceof ProguardWhyAreYouKeepingRule) {
markClass(clazz, rule, ifRulePreconditionMatch);
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, fieldKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingOverriddenMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, fieldKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardAssumeNoSideEffectRule
|| rule instanceof ProguardAssumeValuesRule) {
if (assumeInfoCollectionBuilder != null) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch, timing);
markMatchingOverriddenMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch, timing);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, fieldKeepRules, rule, null, true, true, ifRulePreconditionMatch, timing);
}
} else if (rule instanceof NoFieldTypeStrengtheningRule
|| rule instanceof NoRedundantFieldLoadEliminationRule) {
- markMatchingFields(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
+ markMatchingFields(clazz, fieldKeepRules, rule, ifRulePreconditionMatch);
} else if (rule instanceof InlineRule
|| rule instanceof KeepConstantArgumentRule
|| rule instanceof KeepUnusedReturnValueRule
@@ -492,7 +482,7 @@
|| rule instanceof ReprocessMethodRule
|| rule instanceof WhyAreYouNotInliningRule
|| rule.isMaximumRemovedAndroidLogLevelRule()) {
- markMatchingMethods(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
+ markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
} else if (rule instanceof ClassInlineRule
|| rule instanceof NoUnusedInterfaceRemovalRule
|| rule instanceof NoVerticalClassMergingRule
@@ -503,15 +493,15 @@
}
} else if (rule instanceof NoValuePropagationRule) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, fieldKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardIdentifierNameStringRule) {
- markMatchingFields(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
- markMatchingMethods(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
+ markMatchingFields(clazz, fieldKeepRules, rule, ifRulePreconditionMatch);
+ markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
} else {
assert rule instanceof ConvertCheckNotNullRule;
- markMatchingMethods(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
+ markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
}
}
@@ -524,37 +514,101 @@
if (rule.getClassNames().hasSpecificTypes()) {
// This keep rule only lists specific type matches.
// This means there is no need to iterate over all classes.
- timing.begin("Process rule");
+ timing.begin("Process rule with specific types");
for (DexType type : rule.getClassNames().getSpecificTypes()) {
DexClass clazz = application.definitionFor(type);
// Ignore keep rule iff it does not reference a class in the app.
if (clazz != null) {
- process(clazz, rule, ifRulePreconditionMatch);
+ process(clazz, rule, ifRulePreconditionMatch, timing);
}
}
timing.end();
- return;
+ } else if (rule.isOnlyApplicableToProgramClasses() && !rule.hasMemberRules()) {
+ timing.begin("Fork rule evaluation ST");
+ evaluateRuleConcurrentlySingleTask(tasks, rule, ifRulePreconditionMatch, timing);
+ timing.end();
+ } else {
+ timing.begin("Fork rule evaluation");
+ evaluateRuleConcurrently(tasks, rule, ifRulePreconditionMatch, timing);
+ timing.end();
+ }
+ }
+
+ private void evaluateRuleConcurrently(
+ TaskCollection<?> tasks,
+ ProguardConfigurationRule rule,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing)
+ throws ExecutionException {
+ int batchSize =
+ (int)
+ Math.ceil(
+ (double) application.classes().size() / tasks.getNumberOfThreadsOrDefault(1));
+ List<DexClass> classes = new ArrayList<>(batchSize);
+ rule.forEachRelevantCandidate(
+ appView,
+ subtypingInfo,
+ application.classes(),
+ alwaysTrue(),
+ clazz -> {
+ classes.add(clazz);
+ if (classes.size() == batchSize) {
+ List<DexClass> job = new ArrayList<>(classes);
+ tasks.submitUnchecked(
+ () -> {
+ Timing threadTiming =
+ timing.createThreadTiming("Process rule: " + rule, options);
+ process(job, rule, ifRulePreconditionMatch, threadTiming);
+ threadTiming.end().notifyThreadTimingFinished();
+ });
+ classes.clear();
+ }
+ });
+
+ // Process remaining classes.
+ if (!classes.isEmpty()) {
+ tasks.submitUnchecked(
+ () -> {
+ Timing threadTiming = timing.createThreadTiming("Process rule", options);
+ process(classes, rule, ifRulePreconditionMatch, threadTiming);
+ threadTiming.end().notifyThreadTimingFinished();
+ });
}
- tasks.submit(
+ if (!rule.isOnlyApplicableToProgramClasses()) {
+ tasks.submit(
+ () -> {
+ Timing threadTiming = timing.createThreadTiming("Evaluate non-program", options);
+ if (rule.isApplicableToClasspathClasses()) {
+ for (DexClasspathClass clazz : application.classpathClasses()) {
+ process(clazz, rule, ifRulePreconditionMatch, threadTiming);
+ }
+ }
+ if (rule.isApplicableToLibraryClasses()) {
+ for (DexLibraryClass clazz : application.libraryClasses()) {
+ process(clazz, rule, ifRulePreconditionMatch, threadTiming);
+ }
+ }
+ threadTiming.end().notifyThreadTimingFinished();
+ });
+ }
+ }
+
+ private void evaluateRuleConcurrentlySingleTask(
+ TaskCollection<?> tasks,
+ ProguardConfigurationRule rule,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ assert rule.isOnlyApplicableToProgramClasses();
+ tasks.submitUnchecked(
() -> {
- Timing threadTiming = timing.createThreadTiming("Process rule", options);
+ Timing threadTiming = timing.createThreadTiming("Process rule ST", options);
rule.forEachRelevantCandidate(
appView,
subtypingInfo,
application.classes(),
alwaysTrue(),
- clazz -> process(clazz, rule, ifRulePreconditionMatch));
- if (rule.isApplicableToClasspathClasses()) {
- for (DexClasspathClass clazz : application.classpathClasses()) {
- process(clazz, rule, ifRulePreconditionMatch);
- }
- }
- if (rule.isApplicableToLibraryClasses()) {
- for (DexLibraryClass clazz : application.libraryClasses()) {
- process(clazz, rule, ifRulePreconditionMatch);
- }
- }
+ clazz -> process(clazz, rule, ifRulePreconditionMatch, threadTiming));
threadTiming.end().notifyThreadTimingFinished();
});
}
@@ -716,13 +770,37 @@
Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
boolean includeClasspathClasses,
boolean includeLibraryClasses,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ timing.begin("Mark visible methods");
+ markMatchingVisibleMethods(
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ includeClasspathClasses,
+ includeLibraryClasses,
+ ifRulePreconditionMatch);
+ timing.end();
+ }
+
+ private void markMatchingVisibleMethods(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
+ boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
+ if (memberKeepRules.isEmpty()) {
+ return;
+ }
+ // TODO(b/422947619): Evaluate the impact of matching the rule against the superclass.
+ boolean includeSuperclasses = !rule.isTrivialAllClassMatch();
Set<Wrapper<DexMethod>> methodsMarked =
options.forceProguardCompatibility ? null : new HashSet<>();
- Deque<DexClass> worklist = new ArrayDeque<>();
- worklist.add(clazz);
- while (!worklist.isEmpty()) {
- DexClass currentClass = worklist.pop();
+ DexClass currentClass = clazz;
+ do {
if (!includeClasspathClasses && currentClass.isClasspathClass()) {
break;
}
@@ -733,7 +811,7 @@
currentClass.forEachClassMethodMatching(
method ->
method.belongsToVirtualPool()
- || currentClass == clazz
+ || method.getHolderType().isIdenticalTo(clazz.getType())
|| (method.isStatic() && !method.isPrivate() && !method.isInitializer())
|| options.forceProguardCompatibility,
method -> {
@@ -747,13 +825,12 @@
precondition,
ifRulePreconditionMatch);
});
- if (currentClass.superType != null) {
- DexClass dexClass = application.definitionFor(currentClass.superType);
- if (dexClass != null) {
- worklist.add(dexClass);
- }
+ if (currentClass.hasSuperType() && includeSuperclasses) {
+ currentClass = application.definitionFor(currentClass.getSuperType());
+ } else {
+ break;
}
- }
+ } while (currentClass != null);
// TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
// fullmode.
if (clazz.isProgramClass()
@@ -906,13 +983,35 @@
Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
boolean includeClasspathClasses,
boolean includeLibraryClasses,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ timing.begin("Mark overridden methods");
+ markMatchingOverriddenMethods(
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ includeClasspathClasses,
+ includeLibraryClasses,
+ ifRulePreconditionMatch);
+ timing.end();
+ }
+
+ private void markMatchingOverriddenMethods(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
+ boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
+ if (memberKeepRules.isEmpty()) {
+ return;
+ }
Set<DexClass> visited = Sets.newIdentityHashSet();
- Deque<DexClass> worklist = new ArrayDeque<>();
// Intentionally skip the current `clazz`, assuming it's covered by
// markMatchingVisibleMethods.
- worklist.addAll(subtypingInfo.getSubclasses(clazz));
-
+ Deque<DexClass> worklist = new ArrayDeque<>(subtypingInfo.getSubclasses(clazz));
while (!worklist.isEmpty()) {
DexClass currentClass = worklist.poll();
if (!visited.add(currentClass)) {
@@ -940,14 +1039,30 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
clazz.forEachClassMethod(
- method -> {
- DexClass precondition =
- testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
- markMethod(method, memberKeepRules, null, rule, precondition, ifRulePreconditionMatch);
- });
+ method -> markMethod(method, memberKeepRules, null, rule, null, ifRulePreconditionMatch));
+ }
+
+ private void markMatchingVisibleFields(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
+ boolean includeLibraryClasses,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ timing.begin("Mark visible fields");
+ markMatchingVisibleFields(
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ includeClasspathClasses,
+ includeLibraryClasses,
+ ifRulePreconditionMatch);
+ timing.end();
}
private void markMatchingVisibleFields(
@@ -958,6 +1073,11 @@
boolean includeClasspathClasses,
boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
+ if (memberKeepRules.isEmpty()) {
+ return;
+ }
+ // TODO(b/422947619): Evaluate the impact of matching the rule against the superclass.
+ boolean includeSuperclasses = !rule.isTrivialAllClassMatch();
while (clazz != null) {
if (!includeClasspathClasses && clazz.isClasspathClass()) {
return;
@@ -971,7 +1091,11 @@
testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
markField(field, memberKeepRules, rule, precondition, ifRulePreconditionMatch);
});
- clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
+ if (clazz.hasSuperType() && includeSuperclasses) {
+ clazz = application.definitionFor(clazz.superType);
+ } else {
+ break;
+ }
}
}
@@ -979,14 +1103,9 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
clazz.forEachClassField(
- field -> {
- DexClass precondition =
- testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
- markField(field, memberKeepRules, rule, precondition, ifRulePreconditionMatch);
- });
+ field -> markField(field, memberKeepRules, rule, null, ifRulePreconditionMatch));
}
// TODO(b/67934426): Test this code.
@@ -1075,105 +1194,6 @@
return containsAllAnnotations(rule.getClassAnnotations(), clazz);
}
- boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
- if (satisfyExtendsRule(clazz, rule)) {
- return true;
- }
-
- return satisfyImplementsRule(clazz, rule);
- }
-
- boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) {
- if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) {
- return true;
- }
- // It is possible that this class used to inherit from another class X, but no longer does it,
- // because X has been merged into `clazz`.
- return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false);
- }
-
- boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) {
- while (type != null) {
- DexClass clazz = application.definitionFor(type);
- if (clazz == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- // TODO(b/110141157): Should the vertical class merger move annotations from the source to
- // the target class? If so, it is sufficient only to apply the annotation-matcher to the
- // annotations of `class`.
- if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
- AnnotationMatchResult annotationMatchResult =
- containsAllAnnotations(rule.getInheritanceAnnotations(), clazz);
- if (annotationMatchResult != null) {
- handleMatchedAnnotation(annotationMatchResult);
- return true;
- }
- }
- type = clazz.superType;
- }
- return false;
- }
-
- boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) {
- if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) {
- return true;
- }
- // It is possible that this class used to implement an interface I, but no longer does it,
- // because I has been merged into `clazz`.
- return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true);
- }
-
- private boolean anyImplementedInterfaceMatchesImplementsRule(
- DexClass clazz, ProguardConfigurationRule rule) {
- // TODO(herhut): Maybe it would be better to do this breadth first.
- if (clazz == null) {
- return false;
- }
- for (DexType iface : clazz.interfaces.values) {
- DexClass ifaceClass = application.definitionFor(iface);
- if (ifaceClass == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- // TODO(b/110141157): Should the vertical class merger move annotations from the source to
- // the target class? If so, it is sufficient only to apply the annotation-matcher to the
- // annotations of `ifaceClass`.
- if (rule.getInheritanceClassName().matches(iface, appView)) {
- AnnotationMatchResult annotationMatchResult =
- containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass);
- if (annotationMatchResult != null) {
- handleMatchedAnnotation(annotationMatchResult);
- return true;
- }
- }
- if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
- return true;
- }
- }
- if (clazz.superType == null) {
- return false;
- }
- DexClass superClass = application.definitionFor(clazz.superType);
- if (superClass == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- return anyImplementedInterfaceMatchesImplementsRule(superClass, rule);
- }
-
- private boolean anySourceMatchesInheritanceRuleDirectly(
- DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
- // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
- // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
- return appView.getVerticallyMergedClasses() != null
- && appView.getVerticallyMergedClasses().getSourcesFor(clazz.type).stream()
- .filter(
- sourceType ->
- appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
- .anyMatch(rule.getInheritanceClassName()::matches);
- }
-
private boolean allRulesSatisfied(
Collection<ProguardMemberRule> memberKeepRules, DexClass clazz) {
for (ProguardMemberRule rule : memberKeepRules) {
@@ -1673,8 +1693,8 @@
if (rule.getMemberRules().isEmpty()) {
evaluateCheckDiscardClassAndAllMembersRule(clazz, rule);
} else if (clazz.hasFields() || clazz.hasMethods()) {
- markMatchingFields(clazz, rule.getMemberRules(), rule, null, null);
- markMatchingMethods(clazz, rule.getMemberRules(), rule, null, null);
+ markMatchingFields(clazz, rule.getMemberRules(), rule, null);
+ markMatchingMethods(clazz, rule.getMemberRules(), rule, null);
classesWithCheckDiscardedMembers.add(clazz);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index b17e643..5c629c0 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -96,7 +96,6 @@
appView.pruneItems(prunedItems, executorService, timing);
appView.notifyOptimizationFinishedForTesting();
timing.end();
- appView.dexItemFactory().gc();
}
private DirectMappedDexApplication.Builder removeUnused(DirectMappedDexApplication application) {
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
index 0941d2a..5042fc5 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
@@ -107,7 +107,7 @@
}
@Override
- public void onJavaLangReflectProxyNewProxyInstance(
+ public void onJavaLangReflectProxyGetProxyClassOrNewProxyInstance(
Set<DexProgramClass> classes, ProgramMethod context) {
KeepReason reason = KeepReason.reflectiveUseIn(context);
for (DexProgramClass clazz : classes) {
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/KeepAllReflectiveIdentificationEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/KeepAllReflectiveIdentificationEventConsumer.java
index ea733a4..50c601f 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/KeepAllReflectiveIdentificationEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/KeepAllReflectiveIdentificationEventConsumer.java
@@ -59,7 +59,7 @@
}
@Override
- public void onJavaLangReflectProxyNewProxyInstance(
+ public void onJavaLangReflectProxyGetProxyClassOrNewProxyInstance(
Set<DexProgramClass> classes, ProgramMethod context) {
for (DexProgramClass clazz : classes) {
keep(clazz, context);
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentification.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentification.java
index 5d49cb7..8aef51a 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentification.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentification.java
@@ -97,7 +97,8 @@
}
} else if (holder.isIdenticalTo(factory.proxyType)) {
// java.lang.reflect.Proxy
- if (invokedMethod.isIdenticalTo(factory.proxyMethods.newProxyInstance)) {
+ if (invokedMethod.isIdenticalTo(factory.proxyMethods.getProxyClass)
+ || invokedMethod.isIdenticalTo(factory.proxyMethods.newProxyInstance)) {
enqueue(method);
}
} else if (holder.isIdenticalTo(factory.serviceLoaderType)) {
@@ -187,8 +188,9 @@
}
} else if (holder.isIdenticalTo(factory.proxyType)) {
// java.lang.reflect.Proxy
- if (invokedMethod.isIdenticalTo(factory.proxyMethods.newProxyInstance)) {
- handleJavaLangReflectProxyNewProxyInstance(method, invoke);
+ if (invokedMethod.isIdenticalTo(factory.proxyMethods.getProxyClass)
+ || invokedMethod.isIdenticalTo(factory.proxyMethods.newProxyInstance)) {
+ handleJavaLangReflectProxyGetProxyClassOrNewProxyInstance(method, invoke);
return true;
}
} else if (holder.isIdenticalTo(factory.serviceLoaderType)) {
@@ -423,7 +425,7 @@
* Handles reflective uses of {@link java.lang.reflect.Proxy#newProxyInstance(ClassLoader,
* Class[], InvocationHandler)}.
*/
- private void handleJavaLangReflectProxyNewProxyInstance(
+ private void handleJavaLangReflectProxyGetProxyClassOrNewProxyInstance(
ProgramMethod method, InvokeMethod invoke) {
if (!invoke.isInvokeStatic()) {
assert false;
@@ -466,7 +468,7 @@
}
}
if (!classes.isEmpty()) {
- eventConsumer.onJavaLangReflectProxyNewProxyInstance(classes, method);
+ eventConsumer.onJavaLangReflectProxyGetProxyClassOrNewProxyInstance(classes, method);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentificationEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentificationEventConsumer.java
index d6255c3..3c3163b 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentificationEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/ReflectiveIdentificationEventConsumer.java
@@ -24,7 +24,8 @@
void onJavaLangReflectConstructorNewInstance(ProgramMethod initializer, ProgramMethod context);
- void onJavaLangReflectProxyNewProxyInstance(Set<DexProgramClass> classes, ProgramMethod context);
+ void onJavaLangReflectProxyGetProxyClassOrNewProxyInstance(
+ Set<DexProgramClass> classes, ProgramMethod context);
void onJavaUtilConcurrentAtomicAtomicIntegerFieldUpdaterNewUpdater(
ProgramField field, ProgramMethod context);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index f2eea5b..e0a0ee9 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -463,7 +463,7 @@
assert verifyNonRepresentativesRemovedFromApplication(application, syntheticMethodGroups);
timing.begin("Tree fixing");
- DexApplication.Builder<?> builder = application.builder();
+ DexApplication.Builder<?, ?> builder = application.builder();
treeFixer.fixupClasses(deduplicatedClasses);
builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes()));
application = builder.build();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index d29c13f..c1e8b7f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -1287,7 +1287,7 @@
committedProgramTypes = ImmutableList.of();
amendedApplication = application;
} else {
- DexApplication.Builder<?> appBuilder = application.builder();
+ DexApplication.Builder<?, ?> appBuilder = application.builder();
ImmutableList.Builder<DexType> committedProgramTypesBuilder = ImmutableList.builder();
for (SyntheticDefinition<?, ?, ?> definition : pending.definitions.values()) {
if (!removedClasses.contains(definition.getHolder().getType())) {
diff --git a/src/main/java/com/android/tools/r8/threading/TaskCollection.java b/src/main/java/com/android/tools/r8/threading/TaskCollection.java
index 6030de8..97c1508 100644
--- a/src/main/java/com/android/tools/r8/threading/TaskCollection.java
+++ b/src/main/java/com/android/tools/r8/threading/TaskCollection.java
@@ -7,6 +7,7 @@
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ThrowingAction;
import com.android.tools.r8.utils.UncheckedExecutionException;
import com.google.common.util.concurrent.Futures;
@@ -47,6 +48,10 @@
this(options.getThreadingModule(), executorService, initialCapacity);
}
+ public int getNumberOfThreadsOrDefault(int defaultValue) {
+ return ThreadUtils.getNumberOfThreadsOrDefault(executorService, defaultValue);
+ }
+
public <S> void stream(Collection<S> items, Function<S, T> fn, Consumer<T> consumer)
throws ExecutionException {
if (threadingModule.isSingleThreaded()) {
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index b532a2e..89f259b 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -320,9 +320,13 @@
}
public static int getNumberOfThreads(ExecutorService service) {
+ return getNumberOfThreadsOrDefault(service, -1);
+ }
+
+ public static int getNumberOfThreadsOrDefault(ExecutorService service, int defaultValue) {
if (service instanceof ForkJoinPool) {
return ((ForkJoinPool) service).getParallelism();
}
- return -1;
+ return defaultValue;
}
}
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
index c34780a..a5be337 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -82,12 +82,6 @@
public Builder addResourceTable(String path, byte[] bytes, FeatureSplit featureSplit) {
resourceTables.put(new PathAndBytes(bytes, path), featureSplit);
- try {
- ResourceTable resourceTable = ResourceTable.parseFrom(bytes);
- System.currentTimeMillis();
- } catch (InvalidProtocolBufferException e) {
- throw new RuntimeException(e);
- }
return this;
}
diff --git a/src/test/java/com/android/tools/r8/assistant/ServiceLoaderJsonTest.java b/src/test/java/com/android/tools/r8/assistant/ServiceLoaderJsonTest.java
new file mode 100644
index 0000000..7163502
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/assistant/ServiceLoaderJsonTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2025, 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.assistant;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.assistant.postprocessing.ReflectiveOperationJsonParser;
+import com.android.tools.r8.assistant.postprocessing.model.ReflectiveEvent;
+import com.android.tools.r8.assistant.postprocessing.model.ServiceLoaderLoad;
+import com.android.tools.r8.assistant.runtime.ReflectiveOperationJsonLogger;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.shaking.KeepInfoCollectionExported;
+import com.android.tools.r8.utils.Box;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderJsonTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNativeMultidexDexRuntimes().withMaximumApiLevel().build();
+ }
+
+ @Test
+ public void testInstrumentationWithCustomOracle() throws Exception {
+ Path path = Paths.get(temp.newFile().getAbsolutePath());
+ Box<DexItemFactory> factoryBox = new Box<>();
+ testForAssistant()
+ .addProgramClassesAndInnerClasses(ServiceLoaderTestClass.class)
+ .addInstrumentationClasses(Instrumentation.class)
+ .setCustomReflectiveOperationReceiver(Instrumentation.class)
+ .setMinApi(parameters)
+ .addOptionsModification(opt -> factoryBox.set(opt.itemFactory))
+ .compile()
+ .addVmArguments("-Dcom.android.tools.r8.reflectiveJsonLogger=" + path)
+ .run(parameters.getRuntime(), ServiceLoaderTestClass.class)
+ .assertSuccess();
+ List<ReflectiveEvent> reflectiveEvents =
+ new ReflectiveOperationJsonParser(factoryBox.get()).parse(path);
+ Assert.assertEquals(3, reflectiveEvents.size());
+
+ assertTrue(reflectiveEvents.get(0).isServiceLoaderLoad());
+ ServiceLoaderLoad updater0 = reflectiveEvents.get(0).asServiceLoaderLoad();
+ assertEquals(
+ ServiceLoaderTestClass.NameService.class.getName(), updater0.getType().toSourceString());
+
+ assertTrue(reflectiveEvents.get(1).isServiceLoaderLoad());
+ ServiceLoaderLoad updater1 = reflectiveEvents.get(1).asServiceLoaderLoad();
+ assertEquals(
+ ServiceLoaderTestClass.NameService.class.getName(), updater1.getType().toSourceString());
+
+ assertTrue(reflectiveEvents.get(2).isServiceLoaderLoad());
+ ServiceLoaderLoad updater2 = reflectiveEvents.get(2).asServiceLoaderLoad();
+ assertEquals(
+ ServiceLoaderTestClass.NameService.class.getName(), updater2.getType().toSourceString());
+
+ Box<KeepInfoCollectionExported> keepInfoBox = new Box<>();
+ testForR8(parameters)
+ .addProgramClassesAndInnerClasses(ServiceLoaderTestClass.class)
+ .addOptionsModification(
+ opt -> opt.testing.finalKeepInfoCollectionConsumer = keepInfoBox::set)
+ .setMinApi(parameters)
+ .addKeepMainRule(ServiceLoaderTestClass.class)
+ .addKeepRules("-keep class " + ServiceLoaderTestClass.NameService.class.getName())
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .run(parameters.getRuntime(), ServiceLoaderTestClass.class)
+ .assertSuccessWithOutputLines();
+ KeepInfoCollectionExported keepInfoCollectionExported = keepInfoBox.get();
+
+ assertTrue(updater0.isKeptBy(keepInfoCollectionExported));
+ assertTrue(updater1.isKeptBy(keepInfoCollectionExported));
+ assertTrue(updater2.isKeptBy(keepInfoCollectionExported));
+ }
+
+ public static class Instrumentation extends ReflectiveOperationJsonLogger {
+
+ public Instrumentation() throws IOException {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
index e1c3e30..1babaac 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.benchmarks.BenchmarkBase;
import com.android.tools.r8.benchmarks.BenchmarkConfig;
import com.android.tools.r8.utils.LibraryProvidedProguardRulesTestUtils;
+import com.android.tools.r8.utils.timing.Timing;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -49,7 +50,13 @@
.setDumpDependencyPath(dir)
.setFromRevision(16457)
.buildR8WithPartialShrinking(
- ChromeBenchmarks::configurePartial, ChromeBenchmarks::inspectPartial));
+ ChromeBenchmarks::configurePartial, ChromeBenchmarks::inspectPartial),
+ AppDumpBenchmarkBuilder.builder()
+ .setName("ChromeAppTreeShaking")
+ .setDumpDependencyPath(dir)
+ .setFromRevision(16457)
+ .setRuntimeOnly()
+ .buildR8(ChromeBenchmarks::configureTreeShaking));
}
private static void configure(R8FullTestBuilder testBuilder) {
@@ -72,6 +79,24 @@
.allowUnnecessaryDontWarnWildcards();
}
+ private static void configureTreeShaking(R8FullTestBuilder testBuilder) {
+ configure(testBuilder);
+ testBuilder.addOptionsModification(
+ options ->
+ options.getTestingOptions().enqueuerInspector =
+ (appInfo, enqueuerMode) -> {
+ if (appInfo.options().printTimes) {
+ Timing timing = appInfo.app().timing;
+ timing.end(); // End "Create result"
+ timing.end(); // End "Trace application"
+ timing.end(); // End "Enqueuer"
+ timing.end(); // End "Strip unused code"
+ timing.report(); // Report "R8 main"
+ }
+ throw new AbortBenchmarkException();
+ });
+ }
+
private static void inspectPartial(R8PartialTestCompileResult compileResult) {
compileResult.inspectDiagnosticMessages(
diagnostics ->
@@ -101,4 +126,9 @@
public void testChromeAppPartial() throws Exception {
testBenchmarkWithName("ChromeAppPartial");
}
+
+ @Test
+ public void testChromeTreeShaking() throws Exception {
+ testBenchmarkWithName("ChromeTreeShaking");
+ }
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithGetProxyClassTest.java
similarity index 64%
copy from src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
copy to src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithGetProxyClassTest.java
index ecdf0ba..93e146c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithGetProxyClassTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2025, 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.
@@ -10,31 +10,31 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
+import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class InterfaceWithProxyTest extends TestBase {
+public class InterfaceWithGetProxyClassTest extends TestBase {
private static final String EXPECTED = StringUtils.lines("Hello world!");
- private final TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public InterfaceWithProxyTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
- .addInnerClasses(InterfaceWithProxyTest.class)
+ .addInnerClasses(InterfaceWithGetProxyClassTest.class)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
@@ -42,7 +42,7 @@
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
- .addInnerClasses(InterfaceWithProxyTest.class)
+ .addInnerClasses(InterfaceWithGetProxyClassTest.class)
.addKeepMainRule(TestClass.class)
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
@@ -53,17 +53,15 @@
static class TestClass {
- public static void main(String[] args) {
- I obj =
- (I)
- Proxy.newProxyInstance(
- TestClass.class.getClassLoader(),
- new Class<?>[] {I.class},
- (proxy, method, args1) -> {
- System.out.print("Hello");
- return null;
- });
- obj.greet();
+ public static void main(String[] args) throws Exception {
+ Class<?> proxyClass = Proxy.getProxyClass(TestClass.class.getClassLoader(), I.class);
+ InvocationHandler ih =
+ (proxy, method, args1) -> {
+ System.out.print("Hello");
+ return null;
+ };
+ I i = (I) proxyClass.getDeclaredConstructor(InvocationHandler.class).newInstance(ih);
+ i.greet();
new A().greet();
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithNewProxyInstanceTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithNewProxyInstanceTest.java
index ecdf0ba..a512441 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithProxyTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceWithNewProxyInstanceTest.java
@@ -14,27 +14,26 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class InterfaceWithProxyTest extends TestBase {
+public class InterfaceWithNewProxyInstanceTest extends TestBase {
private static final String EXPECTED = StringUtils.lines("Hello world!");
- private final TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public InterfaceWithProxyTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
- .addInnerClasses(InterfaceWithProxyTest.class)
+ .addInnerClasses(InterfaceWithNewProxyInstanceTest.class)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
@@ -42,7 +41,7 @@
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
- .addInnerClasses(InterfaceWithProxyTest.class)
+ .addInnerClasses(InterfaceWithNewProxyInstanceTest.class)
.addKeepMainRule(TestClass.class)
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
index b690ee9..4fc37c5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
@@ -119,6 +119,7 @@
DirectMappedDexApplication finalApp =
inspector
.getApplication()
+ .asLazy()
.toDirect()
.builder()
.replaceLibraryClasses(libHolder.libraryClasses())
diff --git a/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java b/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java
index 55f23f9..90322ea 100644
--- a/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java
@@ -29,11 +29,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class DexCodeDeduppingTest extends TestBase {
- private final TestParameters parameters;
private static final List<String> EXPECTED = ImmutableList.of("foo", "bar", "foo", "bar");
private static final int ONE_CLASS_COUNT = 4;
@@ -42,15 +42,14 @@
private static final int TWO_CLASS_COUNT = 6;
private static final int TWO_CLASS_DEDUPLICATED_COUNT = 3;
+ @Parameter(0)
+ public TestParameters parameters;
+
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withDexRuntimes().withAllApiLevels().build();
}
- public DexCodeDeduppingTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
@Test
public void testR8SingleClass() throws Exception {
R8TestCompileResult compile =
diff --git a/src/test/java/com/android/tools/r8/dex/DexCodeInvokeSuperDeduppingTest.java b/src/test/java/com/android/tools/r8/dex/DexCodeInvokeSuperDeduppingTest.java
new file mode 100644
index 0000000..b5dd40c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/DexCodeInvokeSuperDeduppingTest.java
@@ -0,0 +1,149 @@
+// Copyright (c) 2025, 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.dex;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.DexSegments;
+import com.android.tools.r8.DexSegments.SegmentInfo;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DexCodeInvokeSuperDeduppingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean enableMappingOutput;
+
+ @Parameters(name = "{0}, map output: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimesAndAllApiLevels().build(), BooleanUtils.values());
+ }
+
+ @Test
+ public void testD8MappingOutput() throws Exception {
+ testForD8(parameters.getBackend())
+ .addProgramClasses(Base.class, FooBase.class, BarBase.class, Main.class)
+ .addProgramClassFileData(getProgramClassFileData())
+ .setMinApi(parameters)
+ .release()
+ .applyIf(enableMappingOutput, D8TestBuilder::internalEnableMappingOutput)
+ .compile()
+ .apply(this::inspectCodeSegment)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("FooBase", "BarBase");
+ }
+
+ private List<byte[]> getProgramClassFileData() throws IOException {
+ return ImmutableList.of(
+ transformer(Foo.class)
+ .transformMethodInsnInMethod(
+ "foo",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (opcode == Opcodes.INVOKESPECIAL) {
+ assertEquals(
+ "com/android/tools/r8/dex/DexCodeInvokeSuperDeduppingTest$FooBase", owner);
+ owner = binaryName(Base.class);
+ }
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ })
+ .transform(),
+ transformer(Bar.class)
+ .transformMethodInsnInMethod(
+ "bar",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (opcode == Opcodes.INVOKESPECIAL) {
+ assertEquals(
+ "com/android/tools/r8/dex/DexCodeInvokeSuperDeduppingTest$BarBase", owner);
+ owner = binaryName(Base.class);
+ }
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ })
+ .transform());
+ }
+
+ private void inspectCodeSegment(D8TestCompileResult compileResult) throws Exception {
+ SegmentInfo codeSegmentInfo = getCodeSegmentInfo(compileResult.writeToZip());
+ int expectedCodeItems = 11;
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S) && enableMappingOutput) {
+ // Main.<init> and Base.<init> are canonicalized, and so is FooBase.<init> and BarBase.<init>.
+ assertEquals(expectedCodeItems - 2, codeSegmentInfo.getItemCount());
+ } else {
+ assertEquals(expectedCodeItems, codeSegmentInfo.getItemCount());
+ }
+ }
+
+ public SegmentInfo getCodeSegmentInfo(Path path)
+ throws CompilationFailedException, ResourceException, IOException {
+ DexSegments.Command command = DexSegments.Command.builder().addProgramFiles(path).build();
+ Map<Integer, SegmentInfo> segmentInfoMap = DexSegments.runForTesting(command);
+ return segmentInfoMap.get(Constants.TYPE_CODE_ITEM);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new Foo().foo();
+ new Bar().bar();
+ }
+ }
+
+ abstract static class Base {
+
+ abstract void m();
+ }
+
+ static class FooBase extends Base {
+
+ @Override
+ void m() {
+ System.out.println("FooBase");
+ }
+ }
+
+ static class Foo extends FooBase {
+
+ void foo() {
+ // Symbolic holder transformed to Base class.
+ super.m();
+ }
+ }
+
+ static class BarBase extends Base {
+
+ @Override
+ void m() {
+ System.out.println("BarBase");
+ }
+ }
+
+ static class Bar extends BarBase {
+
+ void bar() {
+ // Symbolic holder transformed to Base class.
+ super.m();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index efbccf0..5058af1 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.ImmediateAppSubtypingInfo;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -463,7 +464,8 @@
application, options, methodSubject, ImmutableList.of(codeA, codeB));
}
- protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) {
+ protected LazyLoadedDexApplication buildApplication(
+ SmaliBuilder builder, InternalOptions options) {
try {
AndroidApp app =
AndroidApp.builder()
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationPrintConfigurationTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationPrintConfigurationTest.java
new file mode 100644
index 0000000..45276b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationPrintConfigurationTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2025, 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.partial;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8PartialTestCompileResult;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartialCompilationPrintConfigurationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultDexRuntime().withMaximumApiLevel().build();
+ }
+
+ @Test
+ public void testBuilder() throws Exception {
+ Path out = temp.newFile("config.txt").toPath();
+ testForR8Partial(parameters)
+ .addR8ExcludedClasses(ExcludedClass.class)
+ .addR8IncludedClasses(IncludedClass.class)
+ .addKeepClassRules(IncludedClass.class)
+ .apply(
+ testBuilder ->
+ testBuilder
+ .getBuilder()
+ .setProguardConfigurationConsumer(new StringConsumer.FileConsumer(out)))
+ .compile();
+ inspectFile(out);
+ }
+
+ @Test
+ public void testKeepRule() throws Exception {
+ R8PartialTestCompileResult compileResult =
+ testForR8Partial(parameters)
+ .addR8ExcludedClasses(ExcludedClass.class)
+ .addR8IncludedClasses(IncludedClass.class)
+ .addKeepClassRules(IncludedClass.class)
+ .addKeepRules("-printconfiguration")
+ .collectStdout()
+ .compile();
+ inspectStdout(compileResult.getStdout());
+ }
+
+ @Test
+ public void testBoth() throws Exception {
+ Path out = temp.newFile("config.txt").toPath();
+ R8PartialTestCompileResult compileResult =
+ testForR8Partial(parameters)
+ .addR8ExcludedClasses(ExcludedClass.class)
+ .addR8IncludedClasses(IncludedClass.class)
+ .addKeepClassRules(IncludedClass.class)
+ .addKeepRules("-printconfiguration")
+ .collectStdout()
+ .apply(
+ testBuilder ->
+ testBuilder
+ .getBuilder()
+ .setProguardConfigurationConsumer(new StringConsumer.FileConsumer(out)))
+ .compile();
+ inspectFile(out);
+ inspectStdout(compileResult.getStdout());
+ }
+
+ private void inspectFile(Path out) throws IOException {
+ assertTrue(Files.exists(out));
+ inspectLines(Files.readAllLines(out));
+ }
+
+ private void inspectStdout(String stdout) {
+ inspectLines(StringUtils.splitLines(stdout));
+ }
+
+ private void inspectLines(List<String> lines) {
+ assertEquals(
+ 1,
+ lines.stream()
+ .filter(line -> line.equals("-keep class " + IncludedClass.class.getTypeName()))
+ .count());
+ }
+
+ static class ExcludedClass {}
+
+ static class IncludedClass {}
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java
index 17ba25b..4fe4ad3 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -34,7 +34,6 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -45,6 +44,7 @@
import com.android.tools.r8.graph.GenericSignature;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.ImmediateAppSubtypingInfo;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.jasmin.JasminBuilder;
@@ -898,8 +898,8 @@
return newJar;
}
- private static DexApplication readApplicationForDexOutput(AndroidApp app, InternalOptions options)
- throws Exception {
+ private static LazyLoadedDexApplication readApplicationForDexOutput(
+ AndroidApp app, InternalOptions options) throws Exception {
assert options.programConsumer == null;
options.programConsumer = DexIndexedConsumer.emptyConsumer();
return new ApplicationReader(app, options, Timing.empty()).read();
@@ -959,7 +959,7 @@
if (optionsConsumer != null) {
optionsConsumer.accept(options);
}
- DexApplication dexApplication = readApplicationForDexOutput(app, options);
+ LazyLoadedDexApplication dexApplication = readApplicationForDexOutput(app, options);
AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(dexApplication.toDirect());
appView.setAppServices(AppServices.builder(appView).build());
return appView;
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index 7ecd693..e929946 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.shaking.FilteredClassPath;
@@ -2704,8 +2705,8 @@
}
public static void disassemble(AndroidApp app, PrintStream ps) throws IOException {
- DexApplication application =
- new ApplicationReader(app, new InternalOptions(), Timing.empty()).read().toDirect();
+ LazyLoadedDexApplication application =
+ new ApplicationReader(app, new InternalOptions(), Timing.empty()).read();
new AssemblyWriter(application, new InternalOptions(), true, false, true).write(ps);
}
diff --git a/tools/perf.py b/tools/perf.py
index 40333ee..0d4d8c0 100755
--- a/tools/perf.py
+++ b/tools/perf.py
@@ -153,6 +153,9 @@
'AGSA': {
'targets': ['r8-full']
},
+ 'ChromeAppTreeShaking': {
+ 'targets': ['r8-full']
+ },
'SystemUIAppTreeShaking': {
'targets': ['r8-full']
},