blob: f27c88aa6e26f180e49e47edf8bbc1efb377310a [file] [log] [blame]
// Copyright (c) 2017, 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.naming;
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
import static com.android.tools.r8.utils.DescriptorUtils.isValidJavaType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.ConstStringJumbo;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class IdentifierMinifierTest extends TestBase {
private final String appFileName;
private final List<String> keepRulesFiles;
private final Consumer<DexInspector> inspection;
public IdentifierMinifierTest(
String test,
List<String> keepRulesFiles,
Consumer<DexInspector> inspection) {
this.appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
this.keepRulesFiles = keepRulesFiles;
this.inspection = inspection;
}
private AndroidApp processedApp;
@Before
public void generateR8ProcessedApp() throws Exception {
Path out = temp.getRoot().toPath();
R8Command.Builder builder =
ToolHelper.addProguardConfigurationConsumer(
R8Command.builder(),
pgConfig -> {
pgConfig.setPrintMapping(true);
pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
})
.setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(appFileName));
processedApp = ToolHelper.runR8(builder.build(), o -> o.debug = false);
}
@Test
public void identiferMinifierTest() throws Exception {
DexInspector dexInspector = new DexInspector(processedApp);
inspection.accept(dexInspector);
}
@Parameters(name = "test: {0} keep: {1}")
public static Collection<Object[]> data() {
List<String> tests = Arrays.asList(
"adaptclassstrings",
"atomicfieldupdater",
"forname",
"getmembers",
"identifiernamestring");
Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
inspections.put("adaptclassstrings:keep-rules-1.txt", IdentifierMinifierTest::test1_rule1);
inspections.put("adaptclassstrings:keep-rules-2.txt", IdentifierMinifierTest::test1_rule2);
inspections.put(
"atomicfieldupdater:keep-rules.txt", IdentifierMinifierTest::test_atomicfieldupdater);
inspections.put("forname:keep-rules.txt", IdentifierMinifierTest::test_forname);
inspections.put("getmembers:keep-rules.txt", IdentifierMinifierTest::test_getmembers);
inspections.put("identifiernamestring:keep-rules-1.txt", IdentifierMinifierTest::test2_rule1);
inspections.put("identifiernamestring:keep-rules-2.txt", IdentifierMinifierTest::test2_rule2);
inspections.put("identifiernamestring:keep-rules-3.txt", IdentifierMinifierTest::test2_rule3);
return NamingTestBase.createTests(tests, inspections);
}
// Without -adaptclassstrings
private static void test1_rule1(DexInspector inspector) {
ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
MethodSubject main = mainClass.method(DexInspector.MAIN);
Code mainCode = main.getMethod().getCode();
verifyPresenceOfConstString(mainCode.asDexCode().instructions);
int renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
assertEquals(0, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
Code barCode = bar.getMethod().getCode();
verifyPresenceOfConstString(barCode.asDexCode().instructions);
renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
assertEquals(0, renamedYetFoundIdentifierCount);
renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
assertEquals(0, renamedYetFoundIdentifierCount);
}
// With -adaptclassstrings *.*A
private static void test1_rule2(DexInspector inspector) {
ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
MethodSubject main = mainClass.method(DexInspector.MAIN);
Code mainCode = main.getMethod().getCode();
verifyPresenceOfConstString(mainCode.asDexCode().instructions);
int renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
assertEquals(0, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
Code barCode = bar.getMethod().getCode();
verifyPresenceOfConstString(barCode.asDexCode().instructions);
renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
assertEquals(1, renamedYetFoundIdentifierCount);
renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
assertEquals(1, renamedYetFoundIdentifierCount);
}
private static void test_atomicfieldupdater(DexInspector inspector) {
ClassSubject mainClass = inspector.clazz("atomicfieldupdater.Main");
MethodSubject main = mainClass.method(DexInspector.MAIN);
Code mainCode = main.getMethod().getCode();
verifyPresenceOfConstString(mainCode.asDexCode().instructions);
ClassSubject a = inspector.clazz("atomicfieldupdater.A");
Set<Instruction> constStringInstructions =
getRenamedMemberIdentifierConstStrings(a, mainCode.asDexCode().instructions);
assertEquals(3, constStringInstructions.size());
}
private static void test_forname(DexInspector inspector) {
ClassSubject mainClass = inspector.clazz("forname.Main");
MethodSubject main = mainClass.method(DexInspector.MAIN);
Code mainCode = main.getMethod().getCode();
verifyPresenceOfConstString(mainCode.asDexCode().instructions);
int renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
assertEquals(1, renamedYetFoundIdentifierCount);
}
private static void test_getmembers(DexInspector inspector) {
ClassSubject mainClass = inspector.clazz("getmembers.Main");
MethodSubject main = mainClass.method(DexInspector.MAIN);
Code mainCode = main.getMethod().getCode();
verifyPresenceOfConstString(mainCode.asDexCode().instructions);
ClassSubject a = inspector.clazz("getmembers.A");
Set<Instruction> constStringInstructions =
getRenamedMemberIdentifierConstStrings(a, mainCode.asDexCode().instructions);
assertEquals(2, constStringInstructions.size());
ClassSubject b = inspector.clazz("getmembers.B");
MethodSubject inliner = b.method("java.lang.String", "inliner", ImmutableList.of());
Code inlinerCode = inliner.getMethod().getCode();
constStringInstructions =
getRenamedMemberIdentifierConstStrings(a, inlinerCode.asDexCode().instructions);
assertEquals(1, constStringInstructions.size());
}
// Without -identifiernamestring
private static void test2_rule1(DexInspector inspector) {
ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
MethodSubject main = mainClass.method(DexInspector.MAIN);
Code mainCode = main.getMethod().getCode();
verifyPresenceOfConstString(mainCode.asDexCode().instructions);
int renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
assertEquals(0, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("identifiernamestring.A");
MethodSubject aInit =
aClass.method("void", "<init>", ImmutableList.of());
Code initCode = aInit.getMethod().getCode();
verifyPresenceOfConstString(initCode.asDexCode().instructions);
renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
assertEquals(0, renamedYetFoundIdentifierCount);
renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
assertEquals(0, renamedYetFoundIdentifierCount);
}
// With -identifiernamestring for annotations and name-based filters
private static void test2_rule2(DexInspector inspector) {
ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
MethodSubject main = mainClass.method(DexInspector.MAIN);
assertTrue(main.isPresent());
Code mainCode = main.getMethod().getCode();
assertTrue(mainCode.isDexCode());
verifyPresenceOfConstString(mainCode.asDexCode().instructions);
int renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
assertEquals(1, renamedYetFoundIdentifierCount);
ClassSubject aClass = inspector.clazz("identifiernamestring.A");
MethodSubject aInit =
aClass.method("void", "<init>", ImmutableList.of());
Code initCode = aInit.getMethod().getCode();
verifyPresenceOfConstString(initCode.asDexCode().instructions);
renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
assertEquals(1, renamedYetFoundIdentifierCount);
renamedYetFoundIdentifierCount =
countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
assertEquals(2, renamedYetFoundIdentifierCount);
}
// With -identifiernamestring for reflective methods
private static void test2_rule3(DexInspector inspector) {
ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
MethodSubject main = mainClass.method(DexInspector.MAIN);
assertTrue(main.isPresent());
Code mainCode = main.getMethod().getCode();
assertTrue(mainCode.isDexCode());
verifyPresenceOfConstString(mainCode.asDexCode().instructions);
ClassSubject b = inspector.clazz("identifiernamestring.B");
Set<Instruction> constStringInstructions =
getRenamedMemberIdentifierConstStrings(b, mainCode.asDexCode().instructions);
assertEquals(2, constStringInstructions.size());
}
private static void verifyPresenceOfConstString(Instruction[] instructions) {
boolean presence =
Arrays.stream(instructions)
.anyMatch(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo);
assertTrue(presence);
}
private static String retrieveString(Instruction instr) {
if (instr instanceof ConstString) {
ConstString cnst = (ConstString) instr;
return cnst.getString().toString();
} else if (instr instanceof ConstStringJumbo) {
ConstStringJumbo cnst = (ConstStringJumbo) instr;
return cnst.getString().toString();
}
return null;
}
private static Stream<Instruction> getConstStringInstructions(Instruction[] instructions) {
return Arrays.stream(instructions)
.filter(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo);
}
private static int countRenamedClassIdentifier(
DexInspector inspector, Instruction[] instructions) {
return getConstStringInstructions(instructions)
.reduce(0, (cnt, instr) -> {
String cnstString = retrieveString(instr);
assertNotNull(cnstString);
if (isValidJavaType(cnstString)) {
ClassSubject classSubject = inspector.clazz(cnstString);
if (classSubject.isRenamed()
&& descriptorToJavaType(classSubject.getFinalDescriptor()).equals(cnstString)) {
return cnt + 1;
}
}
return cnt;
}, Integer::sum);
}
private static int countRenamedClassIdentifier(
DexInspector inspector, DexEncodedField[] fields) {
return Arrays.stream(fields)
.filter(encodedField -> encodedField.getStaticValue() instanceof DexValueString)
.reduce(0, (cnt, encodedField) -> {
String cnstString =
((DexValueString) encodedField.getStaticValue()).getValue().toString();
if (isValidJavaType(cnstString)) {
ClassSubject classSubject = inspector.clazz(cnstString);
if (classSubject.isRenamed()
&& descriptorToJavaType(classSubject.getFinalDescriptor()).equals(cnstString)) {
return cnt + 1;
}
}
return cnt;
}, Integer::sum);
}
private static Set<Instruction> getRenamedMemberIdentifierConstStrings(
ClassSubject clazz, Instruction[] instructions) {
Set<Instruction> result = Sets.newIdentityHashSet();
getConstStringInstructions(instructions).forEach(instr -> {
String cnstString = retrieveString(instr);
clazz.forAllMethods(foundMethodSubject -> {
if (foundMethodSubject.getFinalSignature().name.equals(cnstString)) {
result.add(instr);
}
});
clazz.forAllFields(foundFieldSubject -> {
if (foundFieldSubject.getFinalSignature().name.equals(cnstString)) {
result.add(instr);
}
});
});
return result;
}
}