blob: b18935bf6288c750fd7f11584fc2e07011cae59b [file] [log] [blame]
// 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.synthesis;
import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticBackportClass;
import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticLambdaClass;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DesugarGraphConsumer;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanBox;
import com.google.common.collect.ImmutableSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class RepeatedCompilationNestedSyntheticsTest extends TestBase {
private final TestParameters parameters;
private final Backend intermediateBackend;
@Parameterized.Parameters(name = "{0}, intermediate: {1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withDefaultDexRuntime().withMinimumApiLevel().build(),
Backend.values());
}
public RepeatedCompilationNestedSyntheticsTest(
TestParameters parameters, Backend intermediateBackend) {
this.parameters = parameters;
this.intermediateBackend = intermediateBackend;
}
@Test
public void test() throws Exception {
assertEquals(Backend.DEX, parameters.getBackend());
ClassReference syntheticLambdaClass = syntheticLambdaClass(UsesBackport.class, 0);
ImmutableSet<String> expectedClassOutputs =
ImmutableSet.of(descriptor(UsesBackport.class), syntheticLambdaClass.getDescriptor());
Map<String, byte[]> firstCompilation = new HashMap<>();
testForD8(Backend.CF)
// High API level such that only the lambda is desugared.
.setMinApi(AndroidApiLevel.S)
.setIntermediate(true)
.addClasspathClasses(I.class)
.addProgramClasses(UsesBackport.class)
.setProgramConsumer(
new ClassFileConsumer() {
@Override
public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
firstCompilation.put(descriptor, data.copyByteData());
}
@Override
public void finished(DiagnosticsHandler handler) {}
})
.compile();
assertEquals(expectedClassOutputs, firstCompilation.keySet());
Map<String, byte[]> secondCompilation = new HashMap<>();
ImmutableSet.Builder<String> allDescriptors = ImmutableSet.builder();
BooleanBox matched = new BooleanBox(false);
for (Entry<String, byte[]> entry : firstCompilation.entrySet()) {
byte[] bytes = entry.getValue();
Origin origin =
new Origin(Origin.root()) {
@Override
public String part() {
return entry.getKey();
}
};
testForD8(intermediateBackend)
.setMinApi(parameters)
.setIntermediate(true)
.addClasspathClasses(I.class)
.apply(b -> b.getBuilder().addClassProgramData(bytes, origin))
.apply(
b ->
b.getBuilder()
.setDesugarGraphConsumer(
new DesugarGraphConsumer() {
@Override
public void accept(Origin dependent, Origin dependency) {
assertThat(
dependency.toString(), containsString(binaryName(I.class)));
assertThat(
dependent.toString(),
containsString(syntheticLambdaClass.getBinaryName()));
matched.set(true);
}
@Override
public void finished() {}
}))
.applyIf(
intermediateBackend == Backend.CF,
b ->
b.setProgramConsumer(
new ClassFileConsumer() {
@Override
public void accept(
ByteDataView data, String descriptor, DiagnosticsHandler handler) {
secondCompilation.put(descriptor, data.copyByteData());
allDescriptors.add(descriptor);
}
@Override
public void finished(DiagnosticsHandler handler) {}
}),
b ->
b.setProgramConsumer(
new DexFilePerClassFileConsumer() {
@Override
public void accept(
String primaryClassDescriptor,
ByteDataView data,
Set<String> descriptors,
DiagnosticsHandler handler) {
secondCompilation.put(primaryClassDescriptor, data.copyByteData());
allDescriptors.addAll(descriptors);
}
@Override
public void finished(DiagnosticsHandler handler) {}
}))
.compile();
}
assertTrue(matched.get());
// The dex file per class file output should maintain the exact same set of primary descriptors.
if (intermediateBackend == Backend.DEX) {
assertEquals(expectedClassOutputs, secondCompilation.keySet());
}
// The total set of classes should also include the backport. The backport should be
// hygienically placed under the synthetic lambda (not the context of the lambda!).
assertEquals(
ImmutableSet.<String>builder()
.addAll(expectedClassOutputs)
.add(syntheticBackportClass(syntheticLambdaClass, 0).getDescriptor())
.build(),
allDescriptors.build());
testForD8(Backend.DEX)
.setMinApi(parameters)
.addProgramClasses(I.class, TestClass.class)
.applyIf(
intermediateBackend == Backend.CF,
b -> b.addProgramClassFileData(secondCompilation.values()),
b -> b.addProgramDexFileData(secondCompilation.values()))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("1")
.inspect(
inspector -> {
Set<String> descriptors =
inspector.allClasses().stream()
.map(c -> c.getFinalReference().getDescriptor())
.collect(Collectors.toSet());
assertEquals(
ImmutableSet.of(
descriptor(I.class),
descriptor(TestClass.class),
descriptor(UsesBackport.class),
// The merge step will reestablish the original contexts, thus both the lambda
// and the backport are placed under the non-synthetic input class
// UsesBackport.
syntheticBackportClass(UsesBackport.class, 0).getDescriptor(),
syntheticLambdaClass(UsesBackport.class, 1).getDescriptor()),
descriptors);
});
}
interface I {
int compare(boolean b1, boolean b2);
}
static class UsesBackport {
public static I foo() {
return Boolean::compare;
}
}
static class TestClass {
public static void main(String[] args) {
System.out.println(UsesBackport.foo().compare(true, false));
}
}
}