blob: 47408531ff12d3a3f234484aaac095fa783569d0 [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.keepparameternames;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.KeepUnusedArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestShrinkerBuilder;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.LocalVariableTable;
import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.android.tools.r8.utils.codeinspector.TypeSubject;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class KeepParameterNamesTest extends TestBase {
private final TestParameters parameters;
private final boolean keepParameterNames;
private final boolean enableMinification;
@Parameterized.Parameters(name = "{0}, keepparameternames {1}, minification {2}")
public static Collection<Object[]> data() {
return buildParameters(
getTestParameters().withCfRuntimes().build(),
BooleanUtils.values(),
BooleanUtils.values());
}
public KeepParameterNamesTest(
TestParameters parameters, boolean keepParameterNames, boolean enableMinification) {
this.parameters = parameters;
this.keepParameterNames = keepParameterNames;
this.enableMinification = enableMinification;
}
private void checkLocalVariable(
LocalVariableTableEntry localVariable,
int index,
String name,
TypeSubject type,
String signature) {
assertEquals(index, localVariable.index);
assertEquals(name, localVariable.name);
assertEquals(type, localVariable.type);
assertEquals(signature, localVariable.signature);
}
private void checkLocalVariableTable(CodeInspector inspector) {
ClassSubject classSubject = inspector.clazz(Api.class);
assertThat(classSubject, isPresent());
MethodSubject method;
method = classSubject.uniqueMethodWithName("apiNoArgs");
assertThat(method, isPresent());
assertEquals(keepParameterNames, method.hasLocalVariableTable());
if (keepParameterNames) {
LocalVariableTable localVariableTable = method.getLocalVariableTable();
assertEquals(1, localVariableTable.size());
assertEquals("this", localVariableTable.get(0).name);
assertTrue(localVariableTable.get(0).type.is(classSubject));
} else {
assertTrue(method.getLocalVariableTable().isEmpty());
}
method = classSubject.uniqueMethodWithName("apiNoArgsStatic");
assertThat(method, isPresent());
assertFalse(method.hasLocalVariableTable());
assertTrue(method.getLocalVariableTable().isEmpty());
method = classSubject.uniqueMethodWithName("api1");
assertThat(method, isPresent());
assertEquals(keepParameterNames, method.hasLocalVariableTable());
if (keepParameterNames) {
LocalVariableTable localVariableTable = method.getLocalVariableTable();
assertEquals(3, localVariableTable.size());
checkLocalVariable(
localVariableTable.get(0),
0,
"this",
classSubject.asFoundClassSubject().asTypeSubject(),
null);
checkLocalVariable(
localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("int"), null);
checkLocalVariable(
localVariableTable.get(2),
2,
"parameter2",
inspector.getTypeSubject("java.lang.String"),
null);
} else {
assertTrue(method.getLocalVariableTable().isEmpty());
}
method = classSubject.uniqueMethodWithName("api2");
assertThat(method, isPresent());
assertEquals(keepParameterNames, method.hasLocalVariableTable());
if (keepParameterNames) {
LocalVariableTable localVariableTable = method.getLocalVariableTable();
assertEquals(3, localVariableTable.size());
checkLocalVariable(
localVariableTable.get(0),
0,
"this",
classSubject.asFoundClassSubject().asTypeSubject(),
null);
checkLocalVariable(
localVariableTable.get(1), 1, "parameter1", inspector.getTypeSubject("long"), null);
checkLocalVariable(
localVariableTable.get(2), 3, "parameter2", inspector.getTypeSubject("double"), null);
} else {
assertTrue(method.getLocalVariableTable().isEmpty());
}
method = classSubject.uniqueMethodWithName("api3");
assertThat(method, isPresent());
assertEquals(keepParameterNames, method.hasLocalVariableTable());
if (keepParameterNames) {
LocalVariableTable localVariableTable = method.getLocalVariableTable();
assertEquals(3, localVariableTable.size());
checkLocalVariable(
localVariableTable.get(0),
0,
"this",
classSubject.asFoundClassSubject().asTypeSubject(),
null);
checkLocalVariable(
localVariableTable.get(1),
1,
"parameter1",
inspector.getTypeSubject("java.util.List"),
"Ljava/util/List<Ljava/lang/String;>;");
checkLocalVariable(
localVariableTable.get(2),
2,
"parameter2",
inspector.getTypeSubject("java.util.Map"),
"Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;");
} else {
assertTrue(method.getLocalVariableTable().isEmpty());
}
}
@Test
public void testApiKept() throws Exception {
String expectedOutput =
StringUtils.lines(
"In Api.apiNoArgs",
"In Api.apiNoArgsStatic",
"In Api.api1",
"In Api.api2",
"In Api.api3");
testForR8(parameters.getBackend())
.addInnerClasses(KeepParameterNamesTest.class)
.addKeepMainRule(TestClass.class)
.addKeepRules("-keep class " + Api.class.getTypeName() + "{ api*(...); }")
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableUnusedArgumentAnnotations()
.minification(enableMinification)
.apply(this::configureKeepParameterNames)
.compile()
.inspect(this::checkLocalVariableTable)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput);
}
private void checkLocalVariableTableNotKept(CodeInspector inspector) {
ClassSubject classSubject = inspector.clazz(Api.class);
assertThat(classSubject, isPresent());
assertEquals(enableMinification, classSubject.isRenamed());
MethodSubject method;
for (String name : new String[] {"apiNoArgs", "apiNoArgsStatic", "api1", "api2", "api3"}) {
method = classSubject.uniqueMethodWithName(name);
assertThat(method, isPresent());
assertEquals(enableMinification, method.isRenamed());
assertFalse(method.hasLocalVariableTable());
assertTrue(method.getLocalVariableTable().isEmpty());
}
}
@Test
public void testApiNotKept() throws Exception {
String expectedOutput =
StringUtils.lines(
"In Api.apiNoArgs",
"In Api.apiNoArgsStatic",
"In Api.api1",
"In Api.api2",
"In Api.api3");
testForR8(parameters.getBackend())
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableUnusedArgumentAnnotations()
.addInnerClasses(KeepParameterNamesTest.class)
.addKeepMainRule(TestClass.class)
.minification(enableMinification)
.apply(this::configureKeepParameterNames)
.compile()
.inspect(this::checkLocalVariableTableNotKept)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput);
}
private void configureKeepParameterNames(TestShrinkerBuilder builder) {
if (keepParameterNames) {
builder.addKeepRules("-keepparameternames");
}
}
static class TestClass {
public static void main(String[] args) {
Api api = new Api();
api.apiNoArgs();
Api.apiNoArgsStatic();
api.api1(0, "Hello, world!");
api.api2(0, 0.0d);
api.api3(null, null);
}
}
@NeverClassInline
static class Api {
@NeverInline
void apiNoArgs() {
System.out.println("In Api.apiNoArgs");
}
@NeverInline
static void apiNoArgsStatic() {
System.out.println("In Api.apiNoArgsStatic");
}
@NeverInline
@KeepUnusedArguments
void api1(int parameter1, String parameter2) {
try {
// Reflective behaviour to trigger IR building in the enqueuer.
Class.forName("NotFound");
} catch (Exception e) {
}
System.out.println("In Api.api1");
}
@NeverInline
@KeepUnusedArguments
void api2(long parameter1, double parameter2) {
System.out.println("In Api.api2");
}
@NeverInline
@KeepUnusedArguments
void api3(List<String> parameter1, Map<String, Object> parameter2) {
System.out.println("In Api.api3");
}
}
}