blob: 191f6cc9d591a556e06f3843de8f713440b57ddc [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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8Command;
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.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
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 java.io.IOException;
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.concurrent.ExecutionException;
import java.util.function.Consumer;
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 {
private final String appFileName;
private final List<String> keepRulesFiles;
private final Consumer<DexInspector> inspection;
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
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;
}
@Before
public void generateR8ProcessedApp()
throws IOException, ExecutionException, ProguardRuleParserException, CompilationException {
Path out = temp.getRoot().toPath();
R8Command command =
R8Command.builder()
.setOutputPath(out)
.addProgramFiles(Paths.get(appFileName))
.addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
.addProguardConfigurationConsumer(builder -> {
builder.setPrintMapping(true);
builder.setPrintMappingFile(out.resolve(AndroidApp.DEFAULT_PROGUARD_MAP_FILE));
})
.addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
.build();
ToolHelper.runR8(command);
}
@Test
public void identiferMinifierTest() throws Exception {
Path out = temp.getRoot().toPath();
DexInspector dexInspector =
new DexInspector(
out.resolve("classes.dex"),
out.resolve(AndroidApp.DEFAULT_PROGUARD_MAP_FILE).toString());
inspection.accept(dexInspector);
}
@Parameters(name = "test: {0} keep: {1}")
public static Collection<Object[]> data() {
List<String> tests = Arrays.asList("adaptclassstrings");
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);
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 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 int countRenamedClassIdentifier(
DexInspector inspector, Instruction[] instructions) {
return Arrays.stream(instructions)
.filter(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo)
.reduce(0, (cnt, instr) -> {
String cnstString = retrieveString(instr);
if (!DescriptorUtils.isClassDescriptor(cnstString)) {
ClassSubject classSubject = inspector.clazz(cnstString);
if (classSubject.isRenamed()
&& DescriptorUtils.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.staticValue instanceof DexValueString)
.reduce(0, (cnt, encodedField) -> {
String cnstString = ((DexValueString) encodedField.staticValue).getValue().toString();
if (!DescriptorUtils.isClassDescriptor(cnstString)) {
ClassSubject classSubject = inspector.clazz(cnstString);
if (classSubject.isRenamed()
&& DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
.equals(cnstString)) {
return cnt + 1;
}
}
return cnt;
}, Integer::sum);
}
}