Desugared library: don't generate callbacks for live non instantiated types
Bug: b/265905174
Change-Id: I1497363ceab5842f7e09f7fc604c794a49b02ffb
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index f85f8d5..16ad20d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -442,9 +442,8 @@
InterfaceMethodProcessorFacade interfaceDesugaring =
instructionDesugaring.getInterfaceMethodPostProcessingDesugaringD8(
ExcludeDexResources, interfaceProcessor);
- CfPostProcessingDesugaringCollection.create(appView, interfaceDesugaring)
- .postProcessingDesugaring(
- appView.appInfo().classes(), m -> true, eventConsumer, executorService);
+ CfPostProcessingDesugaringCollection.create(appView, interfaceDesugaring, m -> true)
+ .postProcessingDesugaring(appView.appInfo().classes(), eventConsumer, executorService);
methodProcessor.awaitMethodProcessing();
eventConsumer.finalizeDesugaring();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index 8770cd1..e3f1f89 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -21,10 +21,12 @@
public abstract class CfPostProcessingDesugaringCollection {
public static CfPostProcessingDesugaringCollection create(
- AppView<?> appView, InterfaceMethodProcessorFacade interfaceMethodProcessorFacade) {
+ AppView<?> appView,
+ InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
+ Predicate<ProgramMethod> isLiveMethod) {
if (appView.options().desugarState.isOn()) {
return NonEmptyCfPostProcessingDesugaringCollection.create(
- appView, interfaceMethodProcessorFacade);
+ appView, interfaceMethodProcessorFacade, isLiveMethod);
}
return empty();
}
@@ -35,7 +37,6 @@
public abstract void postProcessingDesugaring(
Collection<DexProgramClass> programClasses,
- Predicate<ProgramMethod> isLiveMethod,
CfPostProcessingDesugaringEventConsumer eventConsumer,
ExecutorService executorService)
throws ExecutionException;
@@ -51,7 +52,9 @@
}
public static CfPostProcessingDesugaringCollection create(
- AppView<?> appView, InterfaceMethodProcessorFacade interfaceMethodProcessorFacade) {
+ AppView<?> appView,
+ InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
+ Predicate<ProgramMethod> isLiveMethod) {
ArrayList<CfPostProcessingDesugaring> desugarings = new ArrayList<>();
if (appView.options().machineDesugaredLibrarySpecification.hasRetargeting()
&& !appView.options().isDesugaredLibraryCompilation()) {
@@ -62,7 +65,7 @@
}
DesugaredLibraryAPICallbackSynthesizer apiCallbackSynthesizor =
appView.typeRewriter.isRewriting()
- ? new DesugaredLibraryAPICallbackSynthesizer(appView)
+ ? new DesugaredLibraryAPICallbackSynthesizer(appView, isLiveMethod)
: null;
// At this point the desugaredLibraryAPIConverter is required to be last to generate
// call-backs on the forwarding methods.
@@ -87,7 +90,6 @@
@Override
public void postProcessingDesugaring(
Collection<DexProgramClass> programClasses,
- Predicate<ProgramMethod> isLiveMethod,
CfPostProcessingDesugaringEventConsumer eventConsumer,
ExecutorService executorService)
throws ExecutionException {
@@ -112,7 +114,6 @@
@Override
public void postProcessingDesugaring(
Collection<DexProgramClass> programClasses,
- Predicate<ProgramMethod> isLiveMethod,
CfPostProcessingDesugaringEventConsumer eventConsumer,
ExecutorService executorService)
throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index f96680f..deeb8da 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
@@ -15,6 +16,8 @@
import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.Collections;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
@@ -40,6 +43,8 @@
return new R8PostProcessingDesugaringEventConsumer(additions, desugaring, missingClassConsumer);
}
+ public abstract Set<DexMethod> getNewlyLiveMethods();
+
public abstract void finalizeDesugaring() throws ExecutionException;
public static class D8CfPostProcessingDesugaringEventConsumer
@@ -102,6 +107,11 @@
}
@Override
+ public Set<DexMethod> getNewlyLiveMethods() {
+ return Collections.emptySet();
+ }
+
+ @Override
public void finalizeDesugaring() throws ExecutionException {
assert methodProcessor.verifyNoPendingMethodProcessing();
methodProcessor.newWave();
@@ -153,6 +163,13 @@
}
@Override
+ public Set<DexMethod> getNewlyLiveMethods() {
+ // Note: this answers the newly live methods up until the point where this is called.
+ // This has to be called in between post processing to be deterministic.
+ return additions.getNewlyLiveMethods();
+ }
+
+ @Override
public void finalizeDesugaring() {
// Intentionally empty.
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
index c1e11d4..8f6ad62 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -26,6 +26,7 @@
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ExecutorService;
+import java.util.function.Predicate;
public class DesugaredLibraryAPICallbackSynthesizer implements CfPostProcessingDesugaring {
@@ -35,9 +36,13 @@
private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
private final Set<DexMethod> trackedCallBackAPIs;
- public DesugaredLibraryAPICallbackSynthesizer(AppView<?> appView) {
+ private final Predicate<ProgramMethod> isLiveMethod;
+
+ public DesugaredLibraryAPICallbackSynthesizer(
+ AppView<?> appView, Predicate<ProgramMethod> isLiveMethod) {
this.appView = appView;
this.factory = appView.dexItemFactory();
+ this.isLiveMethod = isLiveMethod;
this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView);
if (appView.options().testing.trackDesugaredAPIConversions) {
trackedCallBackAPIs = Sets.newConcurrentHashSet();
@@ -54,6 +59,7 @@
ExecutorService executorService) {
ProcessorContext processorContext = appView.createProcessorContext();
MainThreadContext mainThreadContext = processorContext.createMainThreadContext();
+ Set<DexMethod> newlyLiveMethods = eventConsumer.getNewlyLiveMethods();
assert noPendingWrappersOrConversions();
for (DexProgramClass clazz : programClasses) {
if (!appView.isAlreadyLibraryDesugared(clazz)) {
@@ -62,6 +68,10 @@
// always be live in R8.
for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
if (shouldRegisterCallback(virtualProgramMethod)) {
+ if (!isLiveMethod(virtualProgramMethod, newlyLiveMethods)) {
+ // This happens for live non instantiated types, library overrides are not live there.
+ continue;
+ }
if (trackedCallBackAPIs != null) {
trackedCallBackAPIs.add(virtualProgramMethod.getReference());
}
@@ -82,6 +92,12 @@
generateTrackingWarnings();
}
+ private boolean isLiveMethod(
+ ProgramMethod virtualProgramMethod, Set<DexMethod> newlyLiveMethods) {
+ return isLiveMethod.test(virtualProgramMethod)
+ || newlyLiveMethods.contains(virtualProgramMethod.getReference());
+ }
+
private boolean noPendingWrappersOrConversions() {
for (DexProgramClass pendingSyntheticClass :
appView.getSyntheticItems().getPendingSyntheticClasses()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 213819d..c44c492 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3910,6 +3910,10 @@
assert old == null || old == clazz;
}
+ public Set<DexMethod> getNewlyLiveMethods() {
+ return liveMethods.keySet();
+ }
+
public void addLiveMethod(ProgramMethod method) {
DexMethod signature = method.getDefinition().getReference();
ProgramMethod old = liveMethods.put(signature, method);
@@ -4526,9 +4530,8 @@
InterfaceMethodProcessorFacade interfaceDesugaring =
desugaring.getInterfaceMethodPostProcessingDesugaringR8(
ExcludeDexResources, liveMethods::contains, interfaceProcessor);
- CfPostProcessingDesugaringCollection.create(appView, interfaceDesugaring)
- .postProcessingDesugaring(
- liveTypes.items, liveMethods::contains, eventConsumer, executorService);
+ CfPostProcessingDesugaringCollection.create(appView, interfaceDesugaring, liveMethods::contains)
+ .postProcessingDesugaring(liveTypes.items, eventConsumer, executorService);
if (syntheticAdditions.isEmpty()) {
return;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/GuavaMultiSetSpliteratorTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/GuavaMultiSetSpliteratorTest.java
new file mode 100644
index 0000000..c84d49f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/GuavaMultiSetSpliteratorTest.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2023, 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.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.Multiset;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GuavaMultiSetSpliteratorTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters()
+ .withDexRuntimes()
+ .withApiLevel(AndroidApiLevel.L)
+ .withAllApiLevels()
+ .build(),
+ getJdk8Jdk11(),
+ ImmutableList.of(D8_L8SHRINK, R8_L8SHRINK));
+ }
+
+ public GuavaMultiSetSpliteratorTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ @Test
+ public void testGuava() throws Throwable {
+ if (!compilationSpecification.isProgramShrink()) {
+ // We need multidex for non shrinking build.
+ Assume.assumeTrue(parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L));
+ }
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addProgramFiles(ToolHelper.DEPS)
+ .addInnerClasses(getClass())
+ .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
+ .allowDiagnosticWarningMessages()
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("17744", "NullPointerException");
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(ImmutableMultiset.of().spliterator().characteristics());
+ try {
+ System.out.println(new MyMultiSet<>().spliterator().characteristics());
+ } catch (Exception e) {
+ System.out.println(e.getClass().getSimpleName());
+ }
+ }
+ }
+
+ public static class MyMultiSet<E> implements Multiset<E> {
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public int count(@Nullable Object element) {
+ return 0;
+ }
+
+ @Override
+ public int add(@Nullable E element, int occurrences) {
+ return 0;
+ }
+
+ @Override
+ public boolean add(E element) {
+ return false;
+ }
+
+ @Override
+ public int remove(@Nullable Object element, int occurrences) {
+ return 0;
+ }
+
+ @Override
+ public boolean remove(@Nullable Object element) {
+ return false;
+ }
+
+ @Override
+ public int setCount(E element, int count) {
+ return 0;
+ }
+
+ @Override
+ public boolean setCount(E element, int oldCount, int newCount) {
+ return false;
+ }
+
+ @Override
+ public Set<E> elementSet() {
+ return null;
+ }
+
+ @Override
+ public Set<Entry<E>> entrySet() {
+ return null;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] ts) {
+ return null;
+ }
+
+ @Override
+ public boolean contains(@Nullable Object element) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> elements) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+ }
+}