blob: e8d7572d06dfe9e7925521904bb24cdfc90257a1 [file] [log] [blame]
// Copyright (c) 2020, 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.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
import com.google.common.collect.ImmutableSet;
import java.util.List;
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 SyntheticBridgeSignaturesTest extends VerticalClassMergerTestBase {
// Try both with and without inlining. If the bridge signatures are not updated properly, and
// inlining is enabled, then there can be issues with our inlining invariants regarding the
// outermost caller. If inlining is disabled, there is a risk that the methods will end up
// having the wrong signatures, or that the generated Proguard maps are incorrect (this will be
// caught by the debugging test, which is carried out by the call to runTestOnInput()).
private final boolean allowInlining;
@Parameters(name = "{1}, inlining: {0}")
public static List<Object[]> data() {
return buildParameters(
BooleanUtils.values(), TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
}
public SyntheticBridgeSignaturesTest(boolean allowInlining, TestParameters parameters) {
super(parameters);
this.allowInlining = allowInlining;
}
@Test
public void test() throws Throwable {
Assume.assumeTrue(
"b/273921056",
parameters.isCfRuntime() || !parameters.getDexRuntimeVersion().isEqualTo(Version.V14_0_0));
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.addOptionsModification(
options -> {
if (!allowInlining) {
options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
}
})
.addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters)
.compile();
compileResult
.inspect(this::inspect)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccess();
if (parameters.isDexRuntime()) {
runDebugTest(TestClass.class, compileResult);
}
}
private void inspectVerticallyMergedClasses(VerticallyMergedClassesInspector inspector) {
inspector.assertMergedIntoSubtype(A.class, B.class);
}
private void inspect(CodeInspector inspector) {
assertThat(inspector.clazz(TestClass.class), isPresent());
assertThat(inspector.clazz(ASub.class), isPresent());
assertThat(inspector.clazz(BSub.class), isPresent());
assertThat(inspector.clazz(A.class), not(isPresent()));
assertThat(inspector.clazz(B.class), not(isPresent()));
}
static class TestClass {
// If A is merged into ASub first, then the synthetic bridge for "void A.m(B)" will originally
// get the signature "void ASub.m(B)". Otherwise, if B is merged into BSub first, then the
// synthetic bridge will get the signature "void BSub.m(A)". In either case, it is important
// that
// the signatures of the bridge methods are updated after all classes have been merged
// vertically.
public static void main(String[] args) {
ASub a = new ASub();
BSub b = new BSub();
a.m(b);
b.m(a);
// Ensure that the instantiations are not dead code eliminated.
escape(a);
escape(b);
}
@NeverInline
static void escape(Object o) {
if (System.currentTimeMillis() < 0) {
System.out.println(o);
}
}
}
private static class A {
public void m(B object) {
System.out.println("In A.m()");
}
}
private static class ASub extends A {}
private static class B {
public void m(A object) {
System.out.println("In B.m()");
}
}
@NoHorizontalClassMerging
private static class BSub extends B {}
}