blob: 1878375026ab4eb98f868d19db40d1d788950139 [file] [log] [blame]
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
class A {
int x = 1;
int a() throws ClassNotFoundException {
// Class D is expected to be kept - vertical class merging or not. The -if rule say that if
// the method A.a is in the output, then class D is needed.
String p = getClass().getPackage().getName();
Class.forName(p + ".D");
return 4;
}
}
class B extends A {
int y = 2;
int b() {
return 5;
}
}
class C extends B {
int z = 3;
int c() {
return 6;
}
}
class D {
}
class Main {
public static void main(String[] args) throws ClassNotFoundException {
C c = new C();
System.out.print("" + c.x + "" + c.y + "" + c.z + "" + c.a() + "" + c.b() + "" + c.c());
}
}
// TODO(b/110141157):
// - Add tests where fields and methods get renamed due to naming conflicts.
// - Add tests where the type in a implements clause has changed.
// - Add tests where the then-clause of an -if rule keeps a class that has been merged into another.
@RunWith(Parameterized.class)
public class IfRuleWithVerticalClassMerging extends TestBase {
private static final List<Class> CLASSES =
ImmutableList.of(A.class, B.class, C.class, D.class, Main.class);
private final Backend backend;
private final boolean enableVerticalClassMerging;
public IfRuleWithVerticalClassMerging(Backend backend, boolean enableVerticalClassMerging) {
this.backend = backend;
this.enableVerticalClassMerging = enableVerticalClassMerging;
}
@Parameters(name = "Backend: {0}, vertical class merging: {1}")
public static Collection<Object[]> data() {
// We don't run this on Proguard, as Proguard does not merge A into B.
return ImmutableList.of(
new Object[] {Backend.DEX, true},
new Object[] {Backend.DEX, false},
new Object[] {Backend.CF, true},
new Object[] {Backend.CF, false});
}
private void configure(InternalOptions options) {
options.enableVerticalClassMerging = enableVerticalClassMerging;
// TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
}
private void check(AndroidApp app) throws Exception {
CodeInspector inspector = new CodeInspector(app);
ClassSubject clazzA = inspector.clazz(A.class);
assertEquals(!enableVerticalClassMerging, clazzA.isPresent());
ClassSubject clazzB = inspector.clazz(B.class);
assertThat(clazzB, isPresent());
ClassSubject clazzD = inspector.clazz(D.class);
assertThat(clazzD, isPresent());
assertEquals("123456", runOnVM(app, Main.class, backend));
}
@Test
public void testMergedClassInIfRule() throws Exception {
// Class C is kept, meaning that it will not be touched.
// Class A will be merged into class B.
String config =
String.join(
System.lineSeparator(),
"-keep class **.Main { public static void main(java.lang.String[]); }",
"-keep class **.C",
"-if class **.A",
"-keep class **.D",
"-dontobfuscate");
check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
}
@Test
public void testMergedClassFieldInIfRule() throws Exception {
// Class C is kept, meaning that it will not be touched.
// Class A will be merged into class B.
// Main.main access A.x, so that field exists satisfying the if rule.
String config =
String.join(
System.lineSeparator(),
"-keep class **.Main { public static void main(java.lang.String[]); }",
"-keep class **.C",
"-if class **.A { int x; }",
"-keep class **.D",
"-dontobfuscate");
check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
}
@Test
public void testMergedClassMethodInIfRule() throws Exception {
// Class C is kept, meaning that it will not be touched.
// Class A will be merged into class B.
// Main.main access A.a(), that method exists satisfying the if rule.
String config =
String.join(
System.lineSeparator(),
"-keep class **.Main { public static void main(java.lang.String[]); }",
"-keep class **.C",
"-if class **.A { int a(); }",
"-keep class **.D",
"-dontobfuscate");
check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
}
}