blob: 073003b5ab86235f474c792283f4a8718ed33f40 [file] [log] [blame]
// Copyright (c) 2019, 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.constructor;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.ProguardTestCompileResult;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
class InitMatchingTestClass {
// Trivial <clinit>
static {
}
int field;
public InitMatchingTestClass(int arg) {
field = arg;
}
}
@RunWith(Parameterized.class)
public class InitMatchingTest extends TestBase {
public static final List<String> INIT_NAMES = ImmutableList.of(
"<random>", "<clinit>", "<init>", "<*init>", "<in*>", "<in*", "*init>", "<1>", "<<1>init>",
"void <clinit>", "void <init>", "void <*init>", "void <in*>", "void <in*", "void *init>",
"void <1>", "void <<1>init>", "public void <init>", "public void <*init>",
"public void <clinit>", "static void <clinit>", "XYZ <clinit>", "private XYZ <init>"
);
@Parameterized.Parameters(name = "{0} \"{1}\"")
public static Collection<Object[]> data() {
return buildParameters(ToolHelper.getBackends(), INIT_NAMES);
}
private final Backend backend;
private final String initName;
public InitMatchingTest(Backend backend, String initName) {
this.backend = backend;
this.initName = initName;
}
private String createKeepRule() {
return "-keep class * { " + initName + "(...); }";
}
private static final List<String> ALLOWED_INIT_NAMES_PG = ImmutableList.of(
"<init>", "void <clinit>", "void <init>", "void <1>", "public void <init>",
"void <*init>", "void <in*>", "void *init>", "public void <*init>",
"void <<1>init>", "public void <clinit>", "static void <clinit>",
"XYZ <clinit>", "private XYZ <init>");
private static final List<String> EFFECTIVE_INIT_NAMES_PG = ImmutableList.of(
"<init>", "void <init>", "public void <init>",
"void <*init>", "void <in*>", "void *init>", "public void <*init>");
private static final List<String> EFFECTIVE_CLINIT_NAMES_PG = ImmutableList.of(
"void <clinit>", "static void <clinit>", "void <*init>", "void *init>");
@BeforeClass
public static void checkPGInitNames() {
assert INIT_NAMES.containsAll(ALLOWED_INIT_NAMES_PG);
assert ALLOWED_INIT_NAMES_PG.containsAll(EFFECTIVE_INIT_NAMES_PG);
assert ALLOWED_INIT_NAMES_PG.containsAll(EFFECTIVE_CLINIT_NAMES_PG);
}
@Test
public void testProguard() throws Exception {
assumeTrue(backend == Backend.CF);
ProguardTestCompileResult result;
try {
result =
testForProguard()
.addProgramClasses(InitMatchingTestClass.class)
.addKeepRules(createKeepRule())
.compile();
if (!ALLOWED_INIT_NAMES_PG.contains(initName)) {
fail("Expect to fail");
}
} catch (CompilationFailedException e) {
assertFalse(ALLOWED_INIT_NAMES_PG.contains(initName));
if (initName.equals("void <in*")) {
assertThat(e.getMessage(), containsString("Missing closing angular bracket"));
} else {
assertThat(e.getMessage(),
containsString("Expecting type and name instead of just '" + initName + "'"));
}
return;
}
result.inspect(this::inspectProguard);
}
private void inspectProguard(CodeInspector inspector) {
ClassSubject classSubject = inspector.clazz(InitMatchingTestClass.class);
assertThat(classSubject, isPresent());
MethodSubject init = classSubject.init(ImmutableList.of("int"));
if (EFFECTIVE_INIT_NAMES_PG.contains(initName)) {
assertThat(init, isPresent());
} else {
assertThat(init, not(isPresent()));
}
MethodSubject clinit = classSubject.clinit();
if (EFFECTIVE_CLINIT_NAMES_PG.contains(initName)) {
assertThat(clinit, isPresent());
} else {
assertThat(clinit, not(isPresent()));
}
}
// "[[access-flag]* void] <[cl]init>" is the only valid format. Plus legitimate back-references.
public static final List<String> ALLOWED_INIT_NAMES = ImmutableList.of(
"<clinit>", "<init>", "void <clinit>", "void <init>", "void <1>",
"public void <init>", "public void <clinit>", "static void <clinit>");
private static final List<String> EFFECTIVE_INIT_NAMES = ImmutableList.of(
"<init>", "void <init>", "public void <init>");
private static final List<String> EFFECTIVE_CLINIT_NAMES = ImmutableList.of(
"<clinit>", "void <clinit>", "static void <clinit>");
@BeforeClass
public static void checkR8InitNames() {
assert INIT_NAMES.containsAll(ALLOWED_INIT_NAMES);
assert ALLOWED_INIT_NAMES.containsAll(EFFECTIVE_INIT_NAMES);
}
@Test
public void testR8() throws Exception {
R8TestCompileResult result;
try {
result =
testForR8(backend)
.addProgramClasses(InitMatchingTestClass.class)
.addKeepRules(createKeepRule())
.compile();
if (!ALLOWED_INIT_NAMES.contains(initName)) {
fail("Expect to fail");
}
} catch (CompilationFailedException e) {
assertFalse(ALLOWED_INIT_NAMES.contains(initName));
if (initName.contains("XYZ")) {
assertThat(e.getCause().getMessage(),
containsString("Expected [access-flag]* void "));
return;
}
assertThat(e.getCause().getMessage(),
containsString("Unexpected character '" + (initName.contains("<") ? "<" : ">") + "'"));
assertThat(e.getCause().getMessage(),
containsString("only allowed in the method name '<init>'"));
return;
}
result
.assertNoMessages()
.inspect(this::inspectR8);
}
private void inspectR8(CodeInspector inspector) {
ClassSubject classSubject = inspector.clazz(InitMatchingTestClass.class);
assertThat(classSubject, isPresent());
MethodSubject init = classSubject.init(ImmutableList.of("int"));
if (EFFECTIVE_INIT_NAMES.contains(initName)) {
assertThat(init, isPresent());
} else {
assertThat(init, not(isPresent()));
}
// We only keep class initializers in debug mode.
MethodSubject clinit = classSubject.clinit();
assertThat(clinit, not(isPresent()));
}
}