blob: 51acd6592f62c1687832cc3d4b0cae5a5ec8c7ec [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.internal.proto;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class Proto2ShrinkingTest extends ProtoShrinkingTestBase {
private static final String CONTAINS_FLAGGED_OFF_FIELD =
"com.android.tools.r8.proto2.Shrinking$ContainsFlaggedOffField";
private static final String EXT_B =
"com.android.tools.r8.proto2.Shrinking$PartiallyUsedWithExtension$ExtB";
private static final String EXT_C =
"com.android.tools.r8.proto2.Shrinking$PartiallyUsedWithExtension$ExtC";
private static final String FLAGGED_OFF_EXTENSION =
"com.android.tools.r8.proto2.Shrinking$HasFlaggedOffExtension$Ext";
private static final String HAS_NO_USED_EXTENSIONS =
"com.android.tools.r8.proto2.Shrinking$HasNoUsedExtensions";
private static final String HAS_REQUIRED_FIELD =
"com.android.tools.r8.proto2.Graph$HasRequiredField";
private static final String PARTIALLY_USED =
"com.android.tools.r8.proto2.Shrinking$PartiallyUsed";
private static final String USED_ROOT = "com.android.tools.r8.proto2.Graph$UsedRoot";
private static final String USED_VIA_HAZZER =
"com.android.tools.r8.proto2.Shrinking$UsedViaHazzer";
private static final String USES_ONLY_REPEATED_FIELDS =
"com.android.tools.r8.proto2.Shrinking$UsesOnlyRepeatedFields";
private static List<Path> PROGRAM_FILES =
ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
private final boolean allowAccessModification;
private final boolean enableMinification;
private final TestParameters parameters;
@Parameterized.Parameters(name = "{2}, allow access modification: {0}, enable minification: {1}")
public static List<Object[]> data() {
return buildParameters(
BooleanUtils.values(),
BooleanUtils.values(),
getTestParameters().withDefaultDexRuntime().withAllApiLevels().build());
}
public Proto2ShrinkingTest(
boolean allowAccessModification, boolean enableMinification, TestParameters parameters) {
this.allowAccessModification = allowAccessModification;
this.enableMinification = enableMinification;
this.parameters = parameters;
}
@Test
public void test() throws Exception {
CodeInspector inputInspector = new CodeInspector(PROGRAM_FILES);
R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramFiles(PROGRAM_FILES)
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
.addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
// TODO(b/173340579): This rule should not be needed to allow shrinking of
// PartiallyUsed$Enum.
.addNoHorizontalClassMergingRule(PARTIALLY_USED + "$Enum$1")
.allowAccessModification(allowAccessModification)
.allowDiagnosticMessages()
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
.enableProguardTestOptions()
.enableProtoShrinking()
.minification(enableMinification)
.setMinApi(parameters.getApiLevel())
.compile()
.assertAllInfoMessagesMatch(
containsString("Proguard configuration rule does not match anything"))
.apply(this::inspectWarningMessages)
.inspect(
outputInspector -> {
verifyMapAndRequiredFieldsAreKept(inputInspector, outputInspector);
verifyUnusedExtensionsAreRemoved(inputInspector, outputInspector);
verifyUnusedFieldsAreRemoved(inputInspector, outputInspector);
verifyUnusedHazzerBitFieldsAreRemoved(inputInspector, outputInspector);
verifyUnusedTypesAreRemoved(inputInspector, outputInspector);
})
.run(parameters.getRuntime(), "proto2.TestClass")
.assertSuccessWithOutputLines(
"--- roundtrip ---",
"true",
"123",
"asdf",
"9223372036854775807",
"qwerty",
"--- partiallyUsed_proto2 ---",
"true",
"42",
"--- usedViaHazzer ---",
"true",
"--- usedViaOneofCase ---",
"true",
"--- usesOnlyRepeatedFields ---",
"1",
"--- containsFlaggedOffField ---",
"0",
"--- hasFlaggedOffExtension ---",
"4",
"--- useOneExtension ---",
"42",
"--- keepMapAndRequiredFields ---",
"true",
"10",
"10",
"10");
DexItemFactory dexItemFactory = new DexItemFactory();
ProtoApplicationStats original = new ProtoApplicationStats(dexItemFactory, inputInspector);
ProtoApplicationStats actual =
new ProtoApplicationStats(dexItemFactory, result.inspector(), original);
assertEquals(
ImmutableSet.of(),
actual.getGeneratedExtensionRegistryStats().getSpuriouslyRetainedExtensionFields());
if (ToolHelper.isLocalDevelopment()) {
System.out.println(actual.getStats());
}
}
private void verifyMapAndRequiredFieldsAreKept(
CodeInspector inputInspector, CodeInspector outputInspector) {
// Verify the existence of various fields in the input.
{
ClassSubject usedRootClassSubject = inputInspector.clazz(USED_ROOT);
assertThat(usedRootClassSubject, isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("hasRequiredFieldA_"), isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("hasRequiredFieldB_"), isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("myOneof_"), isPresent());
assertThat(
usedRootClassSubject.uniqueFieldWithName("recursiveWithRequiredField_"), isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("isExtendedWithOptional_"), isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("isExtendedWithScalars_"), isPresent());
assertThat(
usedRootClassSubject.uniqueFieldWithName("isExtendedWithRequiredField_"), isPresent());
assertThat(
usedRootClassSubject.uniqueFieldWithName("isRepeatedlyExtendedWithRequiredField_"),
isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("hasMapField_"), isPresent());
ClassSubject hasRequiredFieldClassSubject = inputInspector.clazz(HAS_REQUIRED_FIELD);
assertThat(hasRequiredFieldClassSubject, isPresent());
assertThat(hasRequiredFieldClassSubject.uniqueFieldWithName("value_"), isPresent());
}
// Verify the existence of various fields in the output.
{
ClassSubject usedRootClassSubject = outputInspector.clazz(USED_ROOT);
assertThat(usedRootClassSubject, isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("hasRequiredFieldA_"), isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("hasRequiredFieldB_"), isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("myOneof_"), isPresent());
assertThat(
usedRootClassSubject.uniqueFieldWithName("recursiveWithRequiredField_"), isPresent());
assertThat(usedRootClassSubject.uniqueFieldWithName("hasMapField_"), isPresent());
assertThat(
usedRootClassSubject.uniqueFieldWithName("isExtendedWithRequiredField_"), isPresent());
assertThat(
usedRootClassSubject.uniqueFieldWithName("isRepeatedlyExtendedWithRequiredField_"),
isPresent());
ClassSubject hasRequiredFieldClassSubject = outputInspector.clazz(HAS_REQUIRED_FIELD);
assertThat(hasRequiredFieldClassSubject, isPresent());
assertThat(hasRequiredFieldClassSubject.uniqueFieldWithName("value_"), isPresent());
}
// Verify the absence of various fields in the output.
{
ClassSubject usedRootClassSubject = outputInspector.clazz(USED_ROOT);
assertThat(usedRootClassSubject, isPresent());
assertThat(
usedRootClassSubject.uniqueFieldWithName("isExtendedWithOptional_"), not(isPresent()));
assertThat(
usedRootClassSubject.uniqueFieldWithName("isExtendedWithScalars_"), not(isPresent()));
}
}
private void verifyUnusedExtensionsAreRemoved(
CodeInspector inputInspector, CodeInspector outputInspector) {
verifyUnusedExtensionsAreRemoved(
inputInspector,
outputInspector,
"com.google.protobuf.proto2_registryGeneratedExtensionRegistryLite");
}
private void verifyUnusedExtensionsAreRemoved(
CodeInspector inputInspector, CodeInspector outputInspector, String extensionRegistryName) {
// Verify that the registry was split across multiple methods in the input.
{
ClassSubject generatedExtensionRegistryLoader = inputInspector.clazz(extensionRegistryName);
assertThat(generatedExtensionRegistryLoader, isPresent());
assertThat(
generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber"),
isPresent());
assertThat(
generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber1"),
isPresent());
assertThat(
generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber2"),
isPresent());
}
// Verify that the registry methods are still present in the output.
//
// We expect findLiteExtensionByNumber2() to be inlined into findLiteExtensionByNumber1(). The
// method findLiteExtensionByNumber1() has two call sites from findLiteExtensionByNumber(),
// which prevents it from being single-caller inlined.
{
ClassSubject generatedExtensionRegistryLoader = outputInspector.clazz(extensionRegistryName);
assertThat(generatedExtensionRegistryLoader, isPresent());
assertThat(
generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber"),
isPresent());
assertThat(
generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber1"),
isPresent());
assertThat(
generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber2"),
notIf(isPresent(), enableMinification));
}
// Verify that unused extensions have been removed with -allowaccessmodification.
if (allowAccessModification) {
List<String> unusedExtensionNames =
ImmutableList.of(FLAGGED_OFF_EXTENSION, HAS_NO_USED_EXTENSIONS, EXT_B, EXT_C);
for (String unusedExtensionName : unusedExtensionNames) {
assertThat(inputInspector.clazz(unusedExtensionName), isPresent());
assertThat(
unusedExtensionName, outputInspector.clazz(unusedExtensionName), not(isPresent()));
}
}
}
private void verifyUnusedFieldsAreRemoved(
CodeInspector inputInspector, CodeInspector outputInspector) {
// Verify that various proto fields are present the input.
{
ClassSubject cfofClassSubject = inputInspector.clazz(CONTAINS_FLAGGED_OFF_FIELD);
assertThat(cfofClassSubject, isPresent());
assertThat(cfofClassSubject.uniqueFieldWithName("conditionallyUsed_"), isPresent());
ClassSubject puClassSubject = inputInspector.clazz(PARTIALLY_USED);
assertThat(puClassSubject, isPresent());
assertEquals(7, puClassSubject.allInstanceFields().size());
assertThat(puClassSubject.uniqueFieldWithName("bitField0_"), isPresent());
assertThat(puClassSubject.uniqueFieldWithName("used_"), isPresent());
assertThat(puClassSubject.uniqueFieldWithName("completelyUnused_"), isPresent());
assertThat(puClassSubject.uniqueFieldWithName("unusedEnum_"), isPresent());
assertThat(puClassSubject.uniqueFieldWithName("unusedRepeatedEnum_"), isPresent());
assertThat(puClassSubject.uniqueFieldWithName("unusedMessage_"), isPresent());
assertThat(puClassSubject.uniqueFieldWithName("unusedRepeatedMessage_"), isPresent());
ClassSubject uvhClassSubject = inputInspector.clazz(USED_VIA_HAZZER);
assertThat(uvhClassSubject, isPresent());
assertThat(uvhClassSubject.uniqueFieldWithName("used_"), isPresent());
assertThat(uvhClassSubject.uniqueFieldWithName("unused_"), isPresent());
}
// Verify that various proto fields have been removed in the output.
{
ClassSubject cfofClassSubject = outputInspector.clazz(CONTAINS_FLAGGED_OFF_FIELD);
assertThat(cfofClassSubject, isPresent());
assertThat(cfofClassSubject.uniqueFieldWithName("conditionallyUsed_"), not(isPresent()));
ClassSubject puClassSubject = outputInspector.clazz(PARTIALLY_USED);
assertThat(puClassSubject, isPresent());
assertThat(puClassSubject.uniqueFieldWithName("bitField0_"), isPresent());
assertThat(puClassSubject.uniqueFieldWithName("used_"), isPresent());
assertThat(puClassSubject.uniqueFieldWithName("completelyUnused_"), not(isPresent()));
assertThat(puClassSubject.uniqueFieldWithName("unusedEnum_"), not(isPresent()));
assertThat(puClassSubject.uniqueFieldWithName("unusedRepeatedEnum_"), not(isPresent()));
assertThat(puClassSubject.uniqueFieldWithName("unusedMessage_"), not(isPresent()));
assertThat(puClassSubject.uniqueFieldWithName("unusedRepeatedMessage_"), not(isPresent()));
ClassSubject uvhClassSubject = outputInspector.clazz(USED_VIA_HAZZER);
assertThat(uvhClassSubject, isPresent());
assertThat(uvhClassSubject.uniqueFieldWithName("used_"), isPresent());
assertThat(uvhClassSubject.uniqueFieldWithName("unused_"), not(isPresent()));
}
}
private void verifyUnusedHazzerBitFieldsAreRemoved(
CodeInspector inputInspector, CodeInspector outputInspector) {
// Verify that various proto fields are present the input.
{
ClassSubject classSubject = inputInspector.clazz(USES_ONLY_REPEATED_FIELDS);
assertThat(classSubject, isPresent());
assertThat(classSubject.uniqueFieldWithName("bitField0_"), isPresent());
assertThat(classSubject.uniqueFieldWithName("myoneof_"), isPresent());
assertThat(classSubject.uniqueFieldWithName("myoneofCase_"), isPresent());
}
// Verify that various proto fields have been removed in the output.
{
ClassSubject classSubject = outputInspector.clazz(USES_ONLY_REPEATED_FIELDS);
assertThat(classSubject, isPresent());
assertThat(classSubject.uniqueFieldWithName("bitField0_"), not(isPresent()));
assertThat(classSubject.uniqueFieldWithName("myoneof_"), not(isPresent()));
assertThat(classSubject.uniqueFieldWithName("myoneofCase_"), not(isPresent()));
}
}
private void verifyUnusedTypesAreRemoved(
CodeInspector inputInspector, CodeInspector outputInspector) {
// Verify that various types are present the input.
{
ClassSubject enumClassSubject = inputInspector.clazz(PARTIALLY_USED + "$Enum");
assertThat(enumClassSubject, isPresent());
ClassSubject nestedClassSubject = inputInspector.clazz(PARTIALLY_USED + "$Nested");
assertThat(nestedClassSubject, isPresent());
}
// Verify that various types have been removed in the output.
{
ClassSubject enumClassSubject = outputInspector.clazz(PARTIALLY_USED + "$Enum");
assertThat(enumClassSubject, not(isPresent()));
ClassSubject nestedClassSubject = outputInspector.clazz(PARTIALLY_USED + "$Nested");
assertThat(nestedClassSubject, not(isPresent()));
}
}
@Test
public void testNoRewriting() throws Exception {
testForR8(parameters.getBackend())
.addProgramFiles(PROGRAM_FILES)
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
// Retain all protos.
.addKeepRules(keepAllProtosRule())
// Retain the signature of dynamicMethod() and newMessageInfo().
.addKeepRules(keepDynamicMethodSignatureRule(), keepNewMessageInfoSignatureRule())
.allowAccessModification(allowAccessModification)
.allowDiagnosticMessages()
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
.enableProtoShrinking()
.minification(enableMinification)
.setMinApi(parameters.getApiLevel())
.compile()
.assertAllInfoMessagesMatch(
containsString("Proguard configuration rule does not match anything"))
.apply(this::inspectWarningMessages)
.inspect(
inspector ->
assertRewrittenProtoSchemasMatch(new CodeInspector(PROGRAM_FILES), inspector));
}
@Test
public void testTwoExtensionRegistrys() throws Exception {
CodeInspector inputInspector = new CodeInspector(PROGRAM_FILES);
testForR8(parameters.getBackend())
.addProgramFiles(PROGRAM_FILES)
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
.addKeepRules(findLiteExtensionByNumberInDuplicateCalledRule())
.addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
// TODO(b/173340579): This rule should not be needed to allow shrinking of
// PartiallyUsed$Enum.
.addNoHorizontalClassMergingRule(PARTIALLY_USED + "$Enum$1")
.allowAccessModification(allowAccessModification)
.allowDiagnosticMessages()
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
.enableProtoShrinking()
.minification(enableMinification)
.setMinApi(parameters.getApiLevel())
.compile()
.assertAllInfoMessagesMatch(
containsString("Proguard configuration rule does not match anything"))
.apply(this::inspectWarningMessages)
.inspect(
outputInspector -> {
verifyUnusedExtensionsAreRemoved(inputInspector, outputInspector);
verifyUnusedExtensionsAreRemoved(
inputInspector,
outputInspector,
"com.google.protobuf.proto2_registryGeneratedExtensionRegistryLiteDuplicate");
});
}
}