blob: a06dea14330fc09d7309ba692746f6e589a49127 [file] [log] [blame]
// Copyright (c) 2016, 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;
import static com.android.tools.r8.DiagnosticsChecker.checkDiagnostics;
import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.shaking.ProguardClassNameList.SingleClassNameList;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
import com.android.tools.r8.shaking.constructor.InitMatchingTest;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
import com.android.tools.r8.utils.KeepingDiagnosticHandler;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
class EmptyMainClassForProguardTests {
public static void main(String[] args) {
}
}
public class ProguardConfigurationParserTest extends TestBase {
private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
private static final String INVALID_PROGUARD_DIR = "src/test/proguard/invalid/";
private static final String PROGUARD_SPEC_FILE = VALID_PROGUARD_DIR + "proguard.flags";
private static final String MULTIPLE_NAME_PATTERNS_FILE =
VALID_PROGUARD_DIR + "multiple-name-patterns.flags";
private static final String ACCESS_FLAGS_FILE = VALID_PROGUARD_DIR + "access-flags.flags";
private static final String WHY_ARE_YOU_KEEPING_FILE =
VALID_PROGUARD_DIR + "why-are-you-keeping.flags";
private static final String ASSUME_NO_SIDE_EFFECTS =
VALID_PROGUARD_DIR + "assume-no-side-effects.flags";
private static final String ASSUME_NO_SIDE_EFFECTS_WITH_RETURN_VALUE =
VALID_PROGUARD_DIR + "assume-no-side-effects-with-return-value.flags";
private static final String ASSUME_VALUES_WITH_RETURN_VALUE =
VALID_PROGUARD_DIR + "assume-values-with-return-value.flags";
private static final String INCLUDING =
VALID_PROGUARD_DIR + "including.flags";
private static final String INVALID_INCLUDING_1 =
INVALID_PROGUARD_DIR + "including-1.flags";
private static final String INVALID_INCLUDING_2 =
INVALID_PROGUARD_DIR + "including-2.flags";
private static final String LIBRARY_JARS =
VALID_PROGUARD_DIR + "library-jars.flags";
private static final String LIBRARY_JARS_WIN =
VALID_PROGUARD_DIR + "library-jars-win.flags";
private static final String SEEDS =
VALID_PROGUARD_DIR + "seeds.flags";
private static final String SEEDS_2 =
VALID_PROGUARD_DIR + "seeds-2.flags";
private static final String VERBOSE =
VALID_PROGUARD_DIR + "verbose.flags";
private static final String KEEPDIRECTORIES =
VALID_PROGUARD_DIR + "keepdirectories.flags";
private static final String DONT_OBFUSCATE =
VALID_PROGUARD_DIR + "dontobfuscate.flags";
private static final String PACKAGE_OBFUSCATION_1 =
VALID_PROGUARD_DIR + "package-obfuscation-1.flags";
private static final String PACKAGE_OBFUSCATION_2 =
VALID_PROGUARD_DIR + "package-obfuscation-2.flags";
private static final String PACKAGE_OBFUSCATION_3 =
VALID_PROGUARD_DIR + "package-obfuscation-3.flags";
private static final String PACKAGE_OBFUSCATION_4 =
VALID_PROGUARD_DIR + "package-obfuscation-4.flags";
private static final String PACKAGE_OBFUSCATION_5 =
VALID_PROGUARD_DIR + "package-obfuscation-5.flags";
private static final String PACKAGE_OBFUSCATION_6 =
VALID_PROGUARD_DIR + "package-obfuscation-6.flags";
private static final String APPLY_MAPPING =
VALID_PROGUARD_DIR + "applymapping.flags";
private static final String APPLY_MAPPING_WITHOUT_FILE =
INVALID_PROGUARD_DIR + "applymapping-without-file.flags";
private static final String DONT_SHRINK =
VALID_PROGUARD_DIR + "dontshrink.flags";
private static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES =
VALID_PROGUARD_DIR + "dontskipnonpubliclibraryclasses.flags";
private static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS =
VALID_PROGUARD_DIR + "dontskipnonpubliclibraryclassmembers.flags";
private static final String IDENTIFIER_NAME_STRING =
VALID_PROGUARD_DIR + "identifiernamestring.flags";
private static final String OVERLOAD_AGGRESIVELY =
VALID_PROGUARD_DIR + "overloadaggressively.flags";
private static final String DONT_OPTIMIZE =
VALID_PROGUARD_DIR + "dontoptimize.flags";
private static final String DONT_OPTIMIZE_OVERRIDES_PASSES =
VALID_PROGUARD_DIR + "dontoptimize-overrides-optimizationpasses.flags";
private static final String OPTIMIZATION_PASSES =
VALID_PROGUARD_DIR + "optimizationpasses.flags";
private static final String OPTIMIZATION_PASSES_WITHOUT_N =
INVALID_PROGUARD_DIR + "optimizationpasses-without-n.flags";
private static final String PARSE_AND_SKIP_SINGLE_ARGUMENT =
VALID_PROGUARD_DIR + "parse-and-skip-single-argument.flags";
private static final String PRINT_USAGE =
VALID_PROGUARD_DIR + "printusage.flags";
private static final String PRINT_USAGE_TO_FILE =
VALID_PROGUARD_DIR + "printusage-to-file.flags";
private static final String TARGET =
VALID_PROGUARD_DIR + "target.flags";
private Reporter reporter;
private KeepingDiagnosticHandler handler;
private ProguardConfigurationParser parser;
private List<String> whiteSpace =
ImmutableList.of("", " ", " ", "\t", " \t", " \t", " \t ", " \t\t \t ");
private List<String> lineSeparators = ImmutableList.of("\n", "\r\n");
private List<Character> quotes = ImmutableList.of('"', '\'');
@Before
public void reset() {
handler = new KeepingDiagnosticHandler();
reporter = new Reporter(handler);
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
}
@Before
public void resetAllowTestOptions() {
handler = new KeepingDiagnosticHandler();
reporter = new Reporter(handler);
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, true);
}
@Test
public void parse() throws Exception {
ProguardConfigurationParser parser;
// Parse from file.
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PROGUARD_SPEC_FILE));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(24, rules.size());
assertEquals(1, rules.get(0).getMemberRules().size());
// Parse from strings.
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
List<String> lines = FileUtils.readAllLines(Paths.get(PROGUARD_SPEC_FILE));
parser.parse(createConfigurationForTesting(lines));
rules = parser.getConfig().getRules();
assertEquals(24, rules.size());
assertEquals(1, rules.get(0).getMemberRules().size());
}
@Test
public void parseMultipleNamePatterns() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(MULTIPLE_NAME_PATTERNS_FILE));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
assertEquals(1, rule.getMemberRules().size());
assertEquals("com.company.hello.**,com.company.world.**", rule.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, ((ProguardKeepRule) rule).getType());
assertTrue(rule.getInheritanceIsExtends());
assertEquals("some.library.Class", rule.getInheritanceClassName().toString());
ProguardMemberRule memberRule = rule.getMemberRules().iterator().next();
assertTrue(memberRule.getAccessFlags().isProtected());
assertEquals(ProguardNameMatcher.create(
IdentifierPatternWithWildcards.withoutWildcards("getContents")), memberRule.getName());
assertEquals("java.lang.Object[][]", memberRule.getType().toString());
assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
assertEquals(0, memberRule.getArguments().size());
}
@Test
public void parseNonJavaIdentifiers() throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, new Reporter());
String nonJavaIdentifiers =
String.join("\n", ImmutableList.of(
"-keep class -package-.-ClassNameWithDash-{",
" -NameWithDash- -field-;",
" p-.-OtherNameWithDash- -method-(-p.-WithDash-, -package-.-ClassNameWithDash-[]); ",
"}"));
parser.parse(createConfigurationForTesting(ImmutableList.of(nonJavaIdentifiers)));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
assertEquals(ProguardClassType.CLASS, rules.get(0).getClassType());
assertEquals(1, rules.get(0).getClassNames().size());
List<DexType> classTypes = rules.get(0).getClassNames().asSpecificDexTypes();
assertEquals(1, classTypes.size());
assertSame(dexItemFactory.createType("L-package-/-ClassNameWithDash-;"), classTypes.get(0));
ProguardConfigurationRule rule = rules.get(0);
assertEquals(2, rule.getMemberRules().size());
int matches = 0;
for (ProguardMemberRule memberRule : rule.getMemberRules()) {
if (memberRule.getRuleType() == ProguardMemberType.FIELD) {
assertTrue(memberRule.getName().matches("-field-"));
matches |= 0x01;
} else {
assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
assertTrue(memberRule.getName().matches("-method-"));
assertFalse(memberRule.getArguments().get(0).getSpecificType().isArrayType());
assertSame(dexItemFactory.createType("L-p/-WithDash-;"),
memberRule.getArguments().get(0).getSpecificType());
assertSame(dexItemFactory.createType("[L-package-/-ClassNameWithDash-;"),
memberRule.getArguments().get(1).getSpecificType());
matches |= 0x02;
}
}
assertEquals(0x03, matches);
}
private void testDontXXX(String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern)
throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
String configuration = "-dont" + xxx + " !foobar,*bar";
parser.parse(createConfigurationForTesting(ImmutableList.of(configuration)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertFalse(pattern.apply(config).matches(dexItemFactory.createType("Lboobaz;")));
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobar;")));
assertFalse(pattern.apply(config).matches(dexItemFactory.createType("Lfoobar;")));
}
@Test
public void testDontXXX() throws Exception {
testDontXXX("warn", ProguardConfiguration::getDontWarnPatterns);
testDontXXX("note", ProguardConfiguration::getDontNotePatterns);
}
private void testDontXXXMultiple(
String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
List<String> configuration1 = ImmutableList.of("-dont" + xxx + " foo.**, bar.**");
List<String> configuration2 = ImmutableList.of("-dont" + xxx + " foo.**", "-dontwarn bar.**");
for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) {
parser.parse(createConfigurationForTesting(configuration));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lfoo/Bar;")));
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lfoo/bar7Bar;")));
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lbar/Foo;")));
}
}
@Test
public void testDontWarnMultiple() throws Exception {
testDontXXXMultiple("warn", ProguardConfiguration::getDontWarnPatterns);
testDontXXXMultiple("note", ProguardConfiguration::getDontNotePatterns);
}
private void testDontXXXAllExplicitly(
String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
String dontwarnAll = "-dont" + xxx + " *";
parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarnAll)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobaz;")));
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobar;")));
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lfoobar;")));
}
@Test
public void testDontWarnAllExplicitly() throws Exception {
testDontXXXAllExplicitly("warn", ProguardConfiguration::getDontWarnPatterns);
testDontXXXAllExplicitly("note", ProguardConfiguration::getDontNotePatterns);
}
private void testDontXXXAllImplicitly(
String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
String dontwarnAll = "-dont" + xxx;
String otherOption = "-keep class *";
parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarnAll, otherOption)));
ProguardConfiguration config = parser.getConfig();
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobaz;")));
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobar;")));
assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lfoobar;")));
}
@Test
public void testDontWarnAllImplicitly() throws Exception {
testDontXXXAllImplicitly("warn", ProguardConfiguration::getDontWarnPatterns);
testDontXXXAllImplicitly("note", ProguardConfiguration::getDontNotePatterns);
}
@Test
public void parseAccessFlags() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ACCESS_FLAGS_FILE));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
ClassAccessFlags publicAndFinalFlags = ClassAccessFlags.fromSharedAccessFlags(0);
publicAndFinalFlags.setPublic();
publicAndFinalFlags.setFinal();
assertTrue(rule.getClassAccessFlags().containsNone(publicAndFinalFlags));
assertTrue(rule.getNegatedClassAccessFlags().containsAll(publicAndFinalFlags));
ClassAccessFlags abstractFlags = ClassAccessFlags.fromSharedAccessFlags(0);
abstractFlags.setAbstract();
assertTrue(rule.getClassAccessFlags().containsAll(abstractFlags));
assertTrue(rule.getNegatedClassAccessFlags().containsNone(abstractFlags));
for (ProguardMemberRule member : rule.getMemberRules()) {
if (member.getRuleType() == ProguardMemberType.ALL_FIELDS) {
FieldAccessFlags publicFlags = FieldAccessFlags.fromSharedAccessFlags(0);
publicFlags.setPublic();
assertTrue(member.getAccessFlags().containsAll(publicFlags));
assertTrue(member.getNegatedAccessFlags().containsNone(publicFlags));
FieldAccessFlags staticFlags = FieldAccessFlags.fromSharedAccessFlags(0);
staticFlags.setStatic();
assertTrue(member.getAccessFlags().containsNone(staticFlags));
assertTrue(member.getNegatedAccessFlags().containsAll(staticFlags));
} else {
assertTrue(member.getRuleType() == ProguardMemberType.ALL_METHODS);
MethodAccessFlags publicNativeFlags = MethodAccessFlags.fromSharedAccessFlags(0, false);
publicNativeFlags.setPublic();
publicNativeFlags.setNative();
assertTrue(member.getAccessFlags().containsAll(publicNativeFlags));
assertFalse(member.getNegatedAccessFlags().containsNone(publicNativeFlags));
MethodAccessFlags protectedNativeFlags = MethodAccessFlags.fromSharedAccessFlags(0, false);
protectedNativeFlags.setProtected();
protectedNativeFlags.setNative();
assertTrue(member.getAccessFlags().containsAll(protectedNativeFlags));
assertFalse(member.getNegatedAccessFlags().containsNone(protectedNativeFlags));
}
}
}
@Test
public void parseWhyAreYouKeeping() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(WHY_ARE_YOU_KEEPING_FILE));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
assertEquals(1, rule.getClassNames().size());
assertEquals("*", rule.getClassNames().toString());
assertTrue(rule.getInheritanceIsExtends());
assertEquals("foo.bar", rule.getInheritanceClassName().toString());
}
@Test
public void parseAssumeNoSideEffects() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> assumeNoSideEffects = parser.getConfig().getRules();
assertEquals(1, assumeNoSideEffects.size());
assumeNoSideEffects.get(0).getMemberRules().forEach(rule -> {
assertFalse(rule.hasReturnValue());
});
}
@Test
public void parseAssumeNoSideEffectsWithReturnValue() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS_WITH_RETURN_VALUE));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> assumeNoSideEffects = parser.getConfig().getRules();
assertEquals(1, assumeNoSideEffects.size());
int matches = 0;
for (ProguardMemberRule rule : assumeNoSideEffects.get(0).getMemberRules()) {
assertTrue(rule.hasReturnValue());
if (rule.getName().matches("returnsTrue") || rule.getName().matches("returnsFalse")) {
assertTrue(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertEquals(rule.getName().matches("returnsTrue"), rule.getReturnValue().getBoolean());
matches |= 1 << 0;
} else if (rule.getName().matches("returns1")) {
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertTrue(rule.getReturnValue().isSingleValue());
assertEquals(1, rule.getReturnValue().getValueRange().getMin());
assertEquals(1, rule.getReturnValue().getValueRange().getMax());
assertEquals(1, rule.getReturnValue().getSingleValue());
matches |= 1 << 1;
} else if (rule.getName().matches("returns2To4")) {
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertFalse(rule.getReturnValue().isSingleValue());
assertEquals(2, rule.getReturnValue().getValueRange().getMin());
assertEquals(4, rule.getReturnValue().getValueRange().getMax());
matches |= 1 << 2;
} else if (rule.getName().matches("returns234To567")) {
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertFalse(rule.getReturnValue().isSingleValue());
assertEquals(234, rule.getReturnValue().getValueRange().getMin());
assertEquals(567, rule.getReturnValue().getValueRange().getMax());
matches |= 1 << 3;
} else if (rule.getName().matches("returnsField")) {
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
assertEquals("int", rule.getReturnValue().getField().type.toString());
assertEquals("X", rule.getReturnValue().getField().name.toString());
matches |= 1 << 4;
} else if (rule.getName().matches("returnsNull")) {
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertTrue(rule.getReturnValue().isNull());
assertTrue(rule.getReturnValue().isSingleValue());
matches |= 1 << 5;
} else {
fail("Unexpected");
}
}
assertEquals((1 << 6) - 1, matches);
}
@Test
public void parseAssumeValuesWithReturnValue() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_VALUES_WITH_RETURN_VALUE));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> assumeValues = parser.getConfig().getRules();
assertEquals(1, assumeValues.size());
int matches = 0;
for (ProguardMemberRule rule : assumeValues.get(0).getMemberRules()) {
assertTrue(rule.hasReturnValue());
if (rule.getName().matches("isTrue") || rule.getName().matches("isFalse")) {
assertTrue(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertEquals(rule.getName().matches("isTrue"), rule.getReturnValue().getBoolean());
matches |= 1 << 0;
} else if (rule.getName().matches("is1")) {
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertTrue(rule.getReturnValue().isSingleValue());
assertEquals(1, rule.getReturnValue().getValueRange().getMin());
assertEquals(1, rule.getReturnValue().getValueRange().getMax());
assertEquals(1, rule.getReturnValue().getSingleValue());
matches |= 1 << 1;
} else if (rule.getName().matches("is2To4")) {
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertFalse(rule.getReturnValue().isSingleValue());
assertEquals(2, rule.getReturnValue().getValueRange().getMin());
assertEquals(4, rule.getReturnValue().getValueRange().getMax());
matches |= 1 << 2;
} else if (rule.getName().matches("is234To567")) {
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertFalse(rule.getReturnValue().isSingleValue());
assertEquals(234, rule.getReturnValue().getValueRange().getMin());
assertEquals(567, rule.getReturnValue().getValueRange().getMax());
matches |= 1 << 3;
} else if (rule.getName().matches("isField")) {
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
assertEquals("int", rule.getReturnValue().getField().type.toString());
assertEquals("X", rule.getReturnValue().getField().name.toString());
matches |= 1 << 4;
} else if (rule.getName().matches("isNull")) {
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
assertTrue(rule.getReturnValue().isNull());
assertTrue(rule.getReturnValue().isSingleValue());
matches |= 1 << 5;
} else {
fail("Unexpected");
}
}
assertEquals((1 << 6) - 1, matches);
}
@Test
public void testAdaptClassStrings() throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
String adaptClassStrings = "-adaptclassstrings !foobar,*bar";
parser.parse(createConfigurationForTesting(ImmutableList.of(adaptClassStrings)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertFalse(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
assertFalse(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
}
@Test
public void testAdaptClassStringsMultiple() throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
List<String> configuration1 = ImmutableList.of("-adaptclassstrings foo.**, bar.**");
List<String> configuration2 =
ImmutableList.of("-adaptclassstrings foo.**", "-adaptclassstrings bar.**");
for (List<String> configuration : ImmutableList.of(configuration1, configuration2)) {
parser.parse(createConfigurationForTesting(configuration));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoo/Bar;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoo/bar7Bar;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lbar/Foo;")));
}
}
@Test
public void testAdaptClassStringsAllExplicitly() throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
String adaptAll = "-adaptclassstrings *";
parser.parse(createConfigurationForTesting(ImmutableList.of(adaptAll)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
}
@Test
public void testAdaptClassStringsAllImplicitly() throws Exception {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
String adaptAll = "-adaptclassstrings";
parser.parse(createConfigurationForTesting(ImmutableList.of(adaptAll)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
}
@Test
public void testIdentifierNameString() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
String config1 =
"-identifiernamestring class a.b.c.*GeneratedClass {\n"
+ " static java.lang.String CONTAINING_TYPE_*;\n"
+ "}";
String config2 =
"-identifiernamestring class x.y.z.ReflectionBasedFactory {\n"
+ " private static java.lang.reflect.Field field(java.lang.Class,java.lang.String);\n"
+ "}";
String config3 =
"-identifiernamestring class * {\n"
+ " @my.annotations.IdentifierNameString *;\n"
+ "}";
parser.parse(createConfigurationForTesting(ImmutableList.of(config1, config2, config3)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
List<ProguardConfigurationRule> identifierNameStrings = config.getRules();
assertEquals(3, identifierNameStrings.size());
assertEquals(1, identifierNameStrings.get(0).getClassNames().size());
assertEquals(
"a.b.c.*GeneratedClass",
identifierNameStrings.get(0).getClassNames().toString());
identifierNameStrings.get(0).getMemberRules().forEach(memberRule -> {
assertTrue(memberRule.getRuleType().includesFields());
assertTrue(memberRule.getName().matches("CONTAINING_TYPE_ABC"));
});
assertEquals(1, identifierNameStrings.get(1).getClassNames().size());
assertEquals(
"x.y.z.ReflectionBasedFactory",
identifierNameStrings.get(1).getClassNames().toString());
identifierNameStrings.get(1).getMemberRules().forEach(memberRule -> {
assertTrue(memberRule.getRuleType().includesMethods());
assertTrue(memberRule.getName().matches("field"));
});
assertEquals(1, identifierNameStrings.get(2).getClassNames().size());
assertEquals("*", identifierNameStrings.get(2).getClassNames().toString());
identifierNameStrings.get(2).getMemberRules().forEach(memberRule -> {
assertEquals(ProguardMemberType.ALL, memberRule.getRuleType());
assertTrue(memberRule.getAnnotation().toString().endsWith("IdentifierNameString"));
});
}
@Test
public void parseDontobfuscate() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_OBFUSCATE));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertFalse(config.isObfuscating());
}
@Test
public void parseRepackageClassesEmpty() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_1));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
assertEquals("", config.getPackagePrefix());
}
@Test
public void parseRepackageClassesNonEmpty() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_2));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
assertEquals("p.q.r", config.getPackagePrefix());
}
@Test
public void parseFlattenPackageHierarchyEmpty() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_3));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
assertEquals("", config.getPackagePrefix());
}
@Test
public void parseFlattenPackageHierarchyNonEmpty() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_4));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
assertEquals("p.q.r", config.getPackagePrefix());
}
@Test
public void flattenPackageHierarchyCannotOverrideRepackageClasses() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(PACKAGE_OBFUSCATION_5);
parser.parse(path);
checkDiagnostics(handler.warnings, path, 6, 1,
"repackageclasses", "overrides", "flattenpackagehierarchy");
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
assertEquals("top", config.getPackagePrefix());
}
@Test
public void repackageClassesOverridesFlattenPackageHierarchy() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(PACKAGE_OBFUSCATION_6);
parser.parse(path);
checkDiagnostics(handler.warnings, path, 6, 1,
"repackageclasses", "overrides", "flattenpackagehierarchy");
ProguardConfiguration config = parser.getConfig();
assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode());
assertNotNull(config.getPackagePrefix());
assertEquals("top", config.getPackagePrefix());
}
@Test
public void parseApplyMapping() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(APPLY_MAPPING));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.hasApplyMappingFile());
}
@Test
public void parseApplyMappingWithoutFile() throws Exception {
Path path = Paths.get(APPLY_MAPPING_WITHOUT_FILE);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(path);
fail("Expect to fail due to the lack of file name.");
} catch (AbortException e) {
checkDiagnostics(handler.errors, path, 6, 14, "File name expected");
}
}
@Test
public void parseIncluding() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(INCLUDING));
verifyParserEndsCleanly();
}
@Test
public void parseInvalidIncluding1() throws IOException {
Path path = Paths.get(INVALID_INCLUDING_1);
try {
new ProguardConfigurationParser(new DexItemFactory(), reporter)
.parse(path);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, path, 6, 10,"does-not-exist.flags");
}
}
@Test
public void parseInvalidIncluding2() {
Path path = Paths.get(INVALID_INCLUDING_2);
try {
new ProguardConfigurationParser(new DexItemFactory(), reporter)
.parse(path);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, path, 6,2, "does-not-exist.flags");
}
}
@Test
public void parseLibraryJars() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
if (!ToolHelper.isLinux() && !ToolHelper.isMac()) {
parser.parse(Paths.get(LIBRARY_JARS_WIN));
} else {
parser.parse(Paths.get(LIBRARY_JARS));
}
assertEquals(4, parser.getConfig().getLibraryjars().size());
}
@Test
public void parseInvalidFilePattern() {
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(
Collections.singletonList("-injars abc.jar(*.zip;*.class)")));
fail();
} catch (AbortException e) {
assertEquals(1, handler.errors.size());
}
}
@Test
public void parseKeepModifiers() {
for (String before : whiteSpace) {
for (String after : whiteSpace) {
reset();
parseAndVerifyParserEndsCleanly(ImmutableList.of(
"-keep"
+ before + "," + after + "includedescriptorclasses"
+ before + "," + after + "allowshrinking"
+ before + "," + after + "allowobfuscation"
+ before + "," + after + "allowoptimization "
+ "class A { *; }"
));
}
}
}
@Test
public void parseKeepAnnotation() {
for (String space : whiteSpace) {
reset();
parseAndVerifyParserEndsCleanly(ImmutableList.of(
"-keep @" + space + "interface A"
));
}
}
@Test
public void regress78442725() {
parseAndVerifyParserEndsCleanly(ImmutableList.of(
"-keep, includedescriptorclasses class in.uncod.android.bypass.Document { *; }",
"-keep, includedescriptorclasses class in.uncod.android.bypass.Element { *; }"
));
}
@Test
public void parseSeeds() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(SEEDS));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintSeeds());
assertNull(config.getSeedFile());
}
@Test
public void parseSeeds2() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(SEEDS_2));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintSeeds());
assertNotNull(config.getSeedFile());
}
@Test
public void parseVerbose() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(VERBOSE));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isVerbose());
}
@Test
public void parseKeepdirectories() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
parser.parse(Paths.get(KEEPDIRECTORIES));
verifyParserEndsCleanly();
}
@Test
public void parseDontshrink() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_SHRINK));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertFalse(config.isShrinking());
}
@Test
public void parseDontSkipNonPublicLibraryClasses() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES));
verifyParserEndsCleanly();
}
@Test
public void parseDontskipnonpubliclibraryclassmembers() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS));
verifyParserEndsCleanly();
}
@Test
public void parseIdentifiernamestring() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path source = Paths.get(IDENTIFIER_NAME_STRING);
parser.parse(source);
verifyParserEndsCleanly();
}
@Test
public void parseOverloadAggressively() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(OVERLOAD_AGGRESIVELY));
verifyParserEndsCleanly();
}
@Test
public void parseDontOptimize() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_OPTIMIZE));
ProguardConfiguration config = parser.getConfig();
verifyParserEndsCleanly();
assertFalse(config.isOptimizing());
}
@Test
public void parseDontOptimizeOverridesPasses() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES);
parser.parse(path);
checkDiagnostics(handler.infos, path, 7, 1, "Ignoring", "-optimizationpasses");
ProguardConfiguration config = parser.getConfig();
assertFalse(config.isOptimizing());
}
@Test
public void parseOptimizationPasses() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(OPTIMIZATION_PASSES);
parser.parse(path);
checkDiagnostics(handler.infos, path, 5, 1, "Ignoring", "-optimizationpasses");
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isOptimizing());
}
@Test
public void parseOptimizationPassesError() {
Path path = Paths.get(OPTIMIZATION_PASSES_WITHOUT_N);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(path);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, path, 6, 1, "Missing n");
}
}
@Test
public void parseSkipNonPublicLibraryClasses() {
testUnsupportedOption("-skipnonpubliclibraryclasses");
}
private void testUnsupportedOption(String option) {
try {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(option)));
fail("Expect to fail due to unsupported option.");
} catch (AbortException e) {
checkDiagnostics(handler.errors, null, 1, 1, "Unsupported option", option);
}
}
@Test
public void parseAndskipSingleArgument() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PARSE_AND_SKIP_SINGLE_ARGUMENT));
verifyParserEndsCleanly();
}
@Test
public void parse_printconfiguration_noArguments() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-printconfiguration"
)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintConfiguration());
assertNull(config.getPrintConfigurationFile());
}
@Test
public void parse_printconfiguration_argument() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-printconfiguration file_name"
)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintConfiguration());
assertEquals("file_name", config.getPrintConfigurationFile().toString());
}
@Test
public void parse_optimizations() throws Exception {
for (String option :
ImmutableList.of(
"-optimizations",
"-optimizations xxx",
"-optimizations \"xxx\"",
"-optimizations 'xxx'",
"-optimizations xxx",
"-optimizations \" xxx\"",
"-optimizations ' xxx'",
"-optimizations xxx/yyy",
"-optimizations \"xxx/yyy\"",
"-optimizations 'xxx/yyy'",
"-optimizations xxx/yyy",
"-optimizations \" xxx/yyy\"",
"-optimizations ' xxx/yyy'",
"-optimizations xxx/yyy,zzz*",
"-optimizations \"xxx/yyy\",\"zzz*\"",
"-optimizations 'xxx/yyy','zzz*'",
"-optimizations xxx/yyy , zzz*",
"-optimizations \"xxx/yyy \",\" zzz*\"",
"-optimizations 'xxx/yyy ',' zzz*'",
"-optimizations !xxx",
"-optimizations \"!xxx\"",
"-optimizations '!xxx'",
"-optimizations ! xxx",
"-optimizations \" ! xxx\"",
"-optimizations ' ! xxx'",
"-optimizations !xxx,!yyy",
"-optimizations \"!xxx\",\"!yyy\"",
"-optimizations '!xxx','!yyy'",
"-optimizations ! xxx, ! yyy",
"-optimizations \" ! xxx\", \" ! yyy\"",
"-optimizations ' ! xxx', ' ! yyy'",
"-optimizations !code/simplification/advanced,code/simplification/*",
"-optimizations \"!code/simplification/advanced\",\"code/simplification/*\"",
"-optimizations '!code/simplification/advanced','code/simplification/*'")) {
reset();
Path proguardConfig = writeTextToTempFile(option);
parser.parse(proguardConfig);
assertEquals(1, handler.infos.size());
checkDiagnostics(handler.infos, proguardConfig, 1, 1, "Ignoring", "-optimizations");
}
}
@Test
public void parse_optimizationpasses() throws Exception {
for (String lineSeparator : lineSeparators) {
reset();
Path proguardConfig = writeTextToTempFile(lineSeparator,
ImmutableList.of(
"-optimizations xxx",
"-optimizationpasses 5",
"-optimizations yyy"));
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
assertEquals(3, handler.infos.size());
checkDiagnostics(handler.infos, 0, proguardConfig, 1, 1, "Ignoring", "-optimizations");
Position p1 = handler.infos.get(0).getPosition();
assertTrue(p1 instanceof TextRange);
TextRange r1 = (TextRange) p1;
assertEquals(1, r1.getStart().getLine());
assertEquals(1, r1.getStart().getColumn());
assertEquals(1, r1.getEnd().getLine());
assertEquals(15, r1.getEnd().getColumn());
checkDiagnostics(
handler.infos, 1, proguardConfig, 2, 1, "Ignoring", "-optimizationpasses");
Position p2 = handler.infos.get(1).getPosition();
assertTrue(p2 instanceof TextRange);
TextRange r2 = (TextRange) p2;
assertEquals(2, r2.getStart().getLine());
assertEquals(1, r2.getStart().getColumn());
assertEquals(2, r2.getEnd().getLine());
assertEquals(22, r2.getEnd().getColumn());
checkDiagnostics(handler.infos, 2, proguardConfig, 3, 1, "Ignoring", "-optimizations");
Position p3 = handler.infos.get(2).getPosition();
assertTrue(p3 instanceof TextRange);
TextRange r3 = (TextRange) p3;
assertEquals(3, r3.getStart().getLine());
assertEquals(1, r3.getStart().getColumn());
assertEquals(3, r3.getEnd().getLine());
assertEquals(15, r3.getEnd().getColumn());
}
}
@Test
public void parsePrintUsage() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PRINT_USAGE));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintUsage());
assertNull(config.getPrintUsageFile());
}
@Test
public void parsePrintUsageToFile() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PRINT_USAGE_TO_FILE));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintUsage());
assertNotNull(config.getPrintUsageFile());
}
@Test
public void parseTarget() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(TARGET));
verifyParserEndsCleanly();
}
@Test
public void parseInvalidKeepOption() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-keepx public class * { ",
" native <methods>; ",
"} "
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 1, 1,
"Unknown option", "-keepx");
}
}
@Test
public void parseKeepOptionEOF() throws Exception {
Path proguardConfig = writeTextToTempFile(
System.lineSeparator(), ImmutableList.of("-keep"), false);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 1, 6,
"Expected [!]interface|@interface|class|enum");
}
}
@Test
public void parseInvalidKeepClassOption() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-keepclassx public class * { ",
" native <methods>; ",
"} "
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 1, 1,
"Unknown option", "-keepclassx");
}
}
@Test
public void parseCustomFlags() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
// Custom Proguard flags -runtype and -laststageoutput are ignored.
Path proguardConfig = writeTextToTempFile(
"-runtype FINAL ",
"-laststageoutput /some/file/name "
);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
}
@Test
public void testRenameSourceFileAttribute() throws Exception {
for (String lineSeparator : lineSeparators) {
reset();
Path proguardConfig = writeTextToTempFile(lineSeparator,
ImmutableList.of(
"-renamesourcefileattribute PG",
"-keepattributes SourceFile,SourceDir"));
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfigRawForTesting();
assertEquals("PG", config.getRenameSourceFileAttribute());
assertTrue(config.getKeepAttributes().sourceFile);
assertTrue(config.getKeepAttributes().sourceDir);
}
}
@Test
public void testRenameSourceFileAttribute_empty() throws Exception {
for (String lineSeparator : lineSeparators) {
reset();
Path proguardConfig = writeTextToTempFile(lineSeparator,
ImmutableList.of(
"-renamesourcefileattribute",
"-keepattributes SourceFile"));
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfigRawForTesting();
assertEquals("", config.getRenameSourceFileAttribute());
assertTrue(config.getKeepAttributes().sourceFile);
assertFalse(config.getKeepAttributes().sourceDir);
}
}
@Test
public void testRenameSourceFileAttribute_qouted() throws Exception {
for (String lineSeparator : lineSeparators) {
for (Character quote : quotes) {
reset();
Path proguardConfig = writeTextToTempFile(lineSeparator,
ImmutableList.of(
"-renamesourcefileattribute " + quote + "PG" + quote,
"-keepattributes SourceFile"));
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfigRawForTesting();
assertEquals("PG", config.getRenameSourceFileAttribute());
assertTrue(config.getKeepAttributes().sourceFile);
assertFalse(config.getKeepAttributes().sourceDir);
}
}
}
@Test
public void testRenameSourceFileAttribute_qouted_empty() throws Exception {
for (String lineSeparator : lineSeparators) {
for (Character quote : quotes) {
reset();
Path proguardConfig = writeTextToTempFile(lineSeparator,
ImmutableList.of(
"-renamesourcefileattribute " + quote + quote,
"-keepattributes SourceFile"));
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfigRawForTesting();
assertEquals("", config.getRenameSourceFileAttribute());
assertTrue(config.getKeepAttributes().sourceFile);
assertFalse(config.getKeepAttributes().sourceDir);
}
}
}
private void testKeepattributes(List<String> expected, String config) {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(config)));
verifyParserEndsCleanly();
assertEquals(
ProguardKeepAttributes.fromPatterns(expected),
parser.getConfigRawForTesting().getKeepAttributes());
}
@Test
public void parseKeepattributes() {
List<String> xxxYYY = ImmutableList.of("xxx", "yyy");
testKeepattributes(xxxYYY, "-keepattributes xxx,yyy");
testKeepattributes(xxxYYY, "-keepattributes \"xxx\",\"yyy\"");
testKeepattributes(xxxYYY, "-keepattributes 'xxx','yyy'");
testKeepattributes(xxxYYY, "-keepattributes xxx, yyy");
testKeepattributes(xxxYYY, "-keepattributes \"xxx\", \"yyy\"");
testKeepattributes(xxxYYY, "-keepattributes 'xxx', 'yyy'");
testKeepattributes(xxxYYY, "-keepattributes xxx ,yyy");
testKeepattributes(xxxYYY, "-keepattributes xxx , yyy");
testKeepattributes(xxxYYY, "-keepattributes \"xxx\" , \"yyy\"");
testKeepattributes(xxxYYY, "-keepattributes 'xxx' , 'yyy'");
testKeepattributes(xxxYYY, "-keepattributes xxx , yyy ");
testKeepattributes(xxxYYY, "-keepattributes \"xxx\" , \"yyy\" ");
testKeepattributes(xxxYYY, "-keepattributes 'xxx' , 'yyy' ");
testKeepattributes(xxxYYY, "-keepattributes xxx , yyy \n");
testKeepattributes(xxxYYY, "-keepattributes \"xxx\" , \"yyy\" \n");
testKeepattributes(xxxYYY, "-keepattributes 'xxx' , 'yyy' \n");
String config =
"-keepattributes Exceptions,InnerClasses,Signature,Deprecated,\n"
+ " SourceFile,LineNumberTable,*Annotation*,EnclosingMethod\n";
List<String> expected = ImmutableList.of(
"Exceptions", "InnerClasses", "Signature", "Deprecated",
"SourceFile", "LineNumberTable", "*Annotation*", "EnclosingMethod");
testKeepattributes(expected, config);
config =
"-keepattributes \"Exceptions\",\"InnerClasses\",\"Signature\",\"Deprecated\",\n"
+ " \"SourceFile\",\"LineNumberTable\",\"*Annotation*\",\"EnclosingMethod\"\n";
testKeepattributes(expected, config);
config =
"-keepattributes 'Exceptions','InnerClasses','Signature','Deprecated',\n"
+ " 'SourceFile','LineNumberTable','*Annotation*','EnclosingMethod'\n";
testKeepattributes(expected, config);
}
private void testKeeppackagenames(ProguardPackageNameList expected, String config) {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(config)));
verifyParserEndsCleanly();
assertEquals(expected, parser.getConfigRawForTesting().getKeepPackageNamesPatterns());
}
@Test
public void parseKeeppackagenames() {
ProguardPackageNameList xxxYYY =
ProguardPackageNameList.builder()
.addPackageName(false, new ProguardPackageMatcher("xxx"))
.addPackageName(false, new ProguardPackageMatcher("yyy"))
.build();
testKeeppackagenames(xxxYYY, "-keeppackagenames xxx,yyy");
testKeeppackagenames(xxxYYY, "-keeppackagenames xxx, yyy");
testKeeppackagenames(xxxYYY, "-keeppackagenames xxx ,yyy");
testKeeppackagenames(xxxYYY, "-keeppackagenames xxx , yyy");
testKeeppackagenames(xxxYYY, "-keeppackagenames xxx , yyy ");
testKeeppackagenames(xxxYYY, "-keeppackagenames xxx , yyy \n");
testKeeppackagenames(xxxYYY, "-keeppackagenames \"xxx\",\"yyy\"");
testKeeppackagenames(
ProguardPackageNameList.builder()
.addPackageName(false, new ProguardPackageMatcher("com.**"))
.addPackageName(false, new ProguardPackageMatcher("org.*"))
.build(),
"-keeppackagenames com.**, org.*");
testKeeppackagenames(
ProguardPackageNameList.builder()
.addPackageName(false, new ProguardPackageMatcher("c?m.**"))
.addPackageName(false, new ProguardPackageMatcher("?r?.*"))
.build(),
"-keeppackagenames c?m.**, ?r?.*");
testKeeppackagenames(
ProguardPackageNameList.builder()
.addPackageName(true, new ProguardPackageMatcher("c?m.**"))
.addPackageName(true, new ProguardPackageMatcher("?r?.*"))
.build(),
"-keeppackagenames !c?m.**, !?r?.*");
}
@Test
public void parseInvalidKeepattributes_brokenList() {
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of("-keepattributes xxx,")));
fail();
} catch (AbortException e) {
assertTrue(
handler.errors.get(0).getDiagnosticMessage().contains("Expected list element at "));
}
}
@Test
public void parseUseUniqueClassMemberNames() throws IOException {
Path proguardConfig = writeTextToTempFile("-useuniqueclassmembernames");
new ProguardConfigurationParser(new DexItemFactory(), reporter).parse(proguardConfig);
checkDiagnostics(
handler.warnings, proguardConfig, 1, 1, "Ignoring", "-useuniqueclassmembernames");
}
@Test
public void parseKeepParameterNames() throws Exception {
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-keepparameternames"
)));
parser.getConfig();
fail();
} catch (AbortException e) {
assertTrue(handler.errors.get(0).getDiagnosticMessage().contains(
"-keepparameternames is not supported"));
}
}
@Test
public void parseKeepParameterNamesWithoutMinification() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-keepparameternames",
"-dontobfuscate"
)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isKeepParameterNames());
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-keepparameternames"
)));
verifyParserEndsCleanly();
parser.parse(createConfigurationForTesting(ImmutableList.of(
"-dontobfuscate"
)));
verifyParserEndsCleanly();
config = parser.getConfig();
assertTrue(config.isKeepParameterNames());
}
@Test
public void parseShortLine() throws IOException {
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(Collections.singletonList("-")));
fail();
} catch (AbortException e) {
assertEquals(1, handler.errors.size());
assertTrue(handler.errors.get(0).getDiagnosticMessage().contains("-"));
}
}
@Test
public void parseNoLocals() throws IOException {
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(Collections.singletonList("--no-locals")));
fail();
} catch (AbortException e) {
assertEquals(1, handler.errors.size());
assertTrue(handler.errors.get(0).getDiagnosticMessage().contains("--no-locals"));
}
}
private void checkFileFilterMatchAnything(ProguardPathFilter filter) {
assertTrue(filter.matches("x"));
assertTrue(filter.matches("x/y"));
assertTrue(filter.matches("x/y/x"));
}
@Test
public void parse_adaptresourcexxx_keepdirectories_noArguments1() {
ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
"-adaptresourcefilenames",
"-adaptresourcefilecontents",
"-keepdirectories"
));
checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
checkFileFilterMatchAnything(config.getKeepDirectories());
}
@Test
public void parse_adaptresourcexxx_keepdirectories_noArguments2() {
ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
"-keepdirectories",
"-adaptresourcefilenames",
"-adaptresourcefilecontents"
));
checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
checkFileFilterMatchAnything(config.getKeepDirectories());
}
@Test
public void parse_adaptresourcexxx_keepdirectories_noArguments3() {
ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
"-adaptresourcefilecontents",
"-keepdirectories",
"-adaptresourcefilenames"
));
checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
checkFileFilterMatchAnything(config.getKeepDirectories());
}
private String FILE_FILTER_SINGLE = "xxx/*";
private void checkFileFilterSingle(ProguardPathFilter filter) {
assertTrue(filter.matches("xxx/x"));
assertTrue(filter.matches("xxx/"));
assertFalse(filter.matches("xxx/yyy/z"));
assertFalse(filter.matches("xxx"));
}
@Test
public void parse_adaptresourcexxx_keepdirectories_singleArgument() {
ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
"-adaptresourcefilenames " + FILE_FILTER_SINGLE,
"-adaptresourcefilecontents " + FILE_FILTER_SINGLE,
"-keepdirectories " + FILE_FILTER_SINGLE
));
checkFileFilterSingle(config.getAdaptResourceFilenames());
checkFileFilterSingle(config.getAdaptResourceFileContents());
checkFileFilterSingle(config.getKeepDirectories());
}
private String FILE_FILTER_MULTIPLE =
"xxx/*, !**.gif ,images/** , com/myapp/**/*.xml,com/mylib/*/*.xml";
private void checkFileFilterMultiple(ProguardPathFilter filter) {
assertTrue(filter.matches("xxx/x"));
assertTrue(filter.matches("xxx/x.gif"));
assertTrue(filter.matches("images/x.jpg"));
assertTrue(filter.matches("images/xxx/x.jpg"));
assertTrue(filter.matches("com/myapp/package1/x.xml"));
assertTrue(filter.matches("com/myapp/package1/package2/x.xml"));
assertTrue(filter.matches("com/mylib/package1/x.xml"));
assertFalse(filter.matches("x.gif"));
assertFalse(filter.matches("images/x.gif"));
assertFalse(filter.matches("images/xxx/y.gif"));
assertFalse(filter.matches("images/xxx/yyy/z.gif"));
assertFalse(filter.matches("com/myapp/package1/x.jpg"));
assertFalse(filter.matches("com/myapp/package1/package2/x.jpg"));
assertFalse(filter.matches("com/mylib/package1/package2/x.xml"));
}
@Test
public void parse_adaptresourcexxx_keepdirectories_multipleArgument() {
ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
"-adaptresourcefilenames " + FILE_FILTER_MULTIPLE,
"-adaptresourcefilecontents " + FILE_FILTER_MULTIPLE,
"-keepdirectories " + FILE_FILTER_MULTIPLE
));
checkFileFilterMultiple(config.getAdaptResourceFilenames());
checkFileFilterMultiple(config.getAdaptResourceFileContents());
checkFileFilterMultiple(config.getKeepDirectories());
}
@Test
public void parse_adaptresourcexxx_keepdirectories_leadingComma() {
List<String> options = ImmutableList.of(
"-adaptresourcefilenames", "-adaptresourcefilecontents", "-keepdirectories");
for (String option : options) {
try {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " ,")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
checkDiagnostics(handler.errors, null, 1, option.length() + 2, "Path filter expected");
}
}
}
@Test
public void parse_adaptresourcexxx_keepdirectories_emptyListElement() {
List<String> options = ImmutableList.of(
"-adaptresourcefilenames", "-adaptresourcefilecontents", "-keepdirectories");
for (String option : options) {
try {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,,yyy")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
}
}
}
@Test
public void parse_adaptresourcexxx_keepdirectories_trailingComma() {
List<String> options = ImmutableList.of(
"-adaptresourcefilenames", "-adaptresourcefilecontents", "-keepdirectories");
for (String option : options) {
try {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,")));
fail("Expect to fail due to the lack of path filter.");
} catch (AbortException e) {
checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected");
}
}
}
@Test
public void parse_testInlineOptions() {
List<String> options = ImmutableList.of(
"-neverinline", "-forceinline");
for (String option : options) {
try {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " class A { *; }")));
fail("Expect to fail due to testing option being turned off.");
} catch (AbortException e) {
assertEquals(1, handler.errors.size());
checkDiagnostics(handler.errors, 0, null, 1, 1, "Unknown option \"" + option + "\"");
}
}
}
@Test
public void parse_if() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$$ModuleAdapter",
"-keep class A",
"-if class **$$InjectAdapter",
"-keep class B",
"-if class **$$StaticInjection",
"-keep class C",
"-keepnames class dagger.Lazy"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
// Three -if rules and one independent -keepnames
assertEquals(4, config.getRules().size());
long ifCount =
config.getRules().stream().filter(rule -> rule instanceof ProguardIfRule).count();
assertEquals(3, ifCount);
ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
assertEquals("**$$ModuleAdapter", if0.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
assertEquals("A", if0.subsequentRule.getClassNames().toString());
ProguardIfRule if1 = (ProguardIfRule) config.getRules().get(1);
assertEquals("**$$InjectAdapter", if1.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if1.subsequentRule.getType());
assertEquals("B", if1.subsequentRule.getClassNames().toString());
ProguardIfRule if2 = (ProguardIfRule) config.getRules().get(2);
assertEquals("**$$StaticInjection", if2.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if2.subsequentRule.getType());
assertEquals("C", if2.subsequentRule.getClassNames().toString());
}
@Test
public void parse_if_nthWildcard() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R**",
"-keep class **$D<2>" // <2> corresponds to the 2nd ** in -if rule.
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
assertEquals("**$R**", if0.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
assertEquals("**$D<2>", if0.subsequentRule.getClassNames().toString());
// Test <2> literally refers to the second wildcard in the rule.
Iterator<ProguardWildcard> it1 = if0.getClassNames().getWildcards().iterator();
it1.next();
ProguardWildcard secondWildcardInIf = it1.next();
assertTrue(secondWildcardInIf.isPattern());
Iterator<ProguardWildcard> it2 = if0.subsequentRule.getWildcards().iterator();
it2.next();
ProguardWildcard backReference = it2.next();
assertTrue(backReference.isBackReference());
assertSame(secondWildcardInIf.asPattern(), backReference.asBackReference().reference);
verifyWithProguard6(proguardConfig);
}
@Test
public void parse_if_fieldType() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **.R*",
"-keep class **.D<2> {",
" <1>.F<2> fld;",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
assertEquals("**.R*", if0.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
assertEquals("**.D<2>", if0.subsequentRule.getClassNames().toString());
assertEquals(1, if0.subsequentRule.getMemberRules().size());
ProguardMemberRule fieldRule = if0.subsequentRule.getMemberRules().get(0);
assertEquals("<1>.F<2>", fieldRule.getType().toString());
verifyWithProguard6(proguardConfig);
}
@Test
public void parse_if_fieldName() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **.R*",
"-keep class **.D<2> {",
" java.lang.String fld<2>;",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
assertEquals("**.R*", if0.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
assertEquals("**.D<2>", if0.subsequentRule.getClassNames().toString());
assertEquals(1, if0.subsequentRule.getMemberRules().size());
ProguardMemberRule fieldRule = if0.subsequentRule.getMemberRules().get(0);
assertEquals("fld<2>", fieldRule.getName().toString());
verifyWithProguard6(proguardConfig);
}
@Test
public void parse_if_returnType() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **.R*",
"-keep class **.D<2> {",
" <1>.M<2> mtd(...);",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
assertEquals("**.R*", if0.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
assertEquals("**.D<2>", if0.subsequentRule.getClassNames().toString());
assertEquals(1, if0.subsequentRule.getMemberRules().size());
ProguardMemberRule methodRule = if0.subsequentRule.getMemberRules().get(0);
assertEquals("<1>.M<2>", methodRule.getType().toString());
verifyWithProguard6(proguardConfig);
}
@Test
public void parse_if_init() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **.R* {",
" void *(...);",
"}",
"-keep class **.D<2> {",
" <3>(...);",
"}"
);
try {
parser.parse(proguardConfig);
fail("Expect to fail due to unsupported constructor name pattern.");
} catch (AbortException e) {
checkDiagnostics(
handler.errors, proguardConfig, 5, 3, "Unexpected character", "method name");
}
verifyFailWithProguard6(proguardConfig, "Expecting type and name instead of just '<3>'");
}
@Test
public void parse_if_methodName_void() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **.R* {",
" void *(...);",
"}",
"-keep class **.D<2> {",
" void <3>_delegate(...);",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
assertEquals("**.R*", if0.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
assertEquals("**.D<2>", if0.subsequentRule.getClassNames().toString());
assertEquals(1, if0.subsequentRule.getMemberRules().size());
ProguardMemberRule methodRule = if0.subsequentRule.getMemberRules().get(0);
assertEquals("<3>_delegate", methodRule.getName().toString());
verifyWithProguard6(proguardConfig);
}
@Test
public void parse_if_methodName_class() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **.R* {",
" ** *(...);",
"}",
"-keep class **.D<2> {",
" <3> <4>(...);",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
assertEquals("**.R*", if0.getClassNames().toString());
assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
assertEquals("**.D<2>", if0.subsequentRule.getClassNames().toString());
assertEquals(1, if0.subsequentRule.getMemberRules().size());
ProguardMemberRule methodRule = if0.subsequentRule.getMemberRules().get(0);
assertEquals("<3>", methodRule.getType().toString());
assertEquals("<4>", methodRule.getName().toString());
verifyWithProguard6(proguardConfig);
}
@Test
public void parse_if_nthWildcard_notNumber_literalN() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R**",
"-keep class **D<n>"
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Use of generics not allowed for java type");
}
verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
}
@Test
public void parse_if_nthWildcard_notNumber_asterisk_inClassName() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R**",
"-keep class **D<*>"
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Use of generics not allowed for java type");
}
verifyFailWithProguard6(proguardConfig, "Use of generics not allowed for java type");
}
@Test
public void parse_if_nthWildcard_notNumber_asterisk_inMemberName() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R**",
"-keep class **D<2> {",
" int id<*>;",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
verifyWithProguard6(proguardConfig);
}
@Test
public void parse_if_nestedAngularBrackets_inMemberName() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R**",
"-keep class **D<2> {",
" int id<<*>>;",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
checkDiagnostics(handler.warnings, proguardConfig, 3, 7, "The field name \"id<<*>>\" is");
verifyWithProguard6(proguardConfig);
}
@Test
public void parse_if_nestedAngularBrackets_outOfRange() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R**",
"-keep class **D<2> {",
" int id<<4>>;", // There are 3 previous referable wildcards in this rule.
"}"
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 4, 2,
"Wildcard", "<4>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
}
@Test
public void parse_if_nthWildcard_outOfRange_tooSmall() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R**",
"-keep class **D<0>" // nth wildcard starts from 1, not 0.
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 2, 13,
"Wildcard", "<0>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (0,");
}
@Test
public void parse_if_nthWildcard_outOfRange_tooBig() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R**",
"-keep class **D<4>" // There are 3 previous wildcards in this rule.
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 3, 1,
"Wildcard", "<4>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (4,");
}
@Test
public void parse_if_nthWildcard_outOfRange_inIf() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$R<2>", // There is only one wildcard prior to <2>.
"-keep class **D<2>"
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 3, 1,
"Wildcard", "<2>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (2,");
}
@Test
public void parse_if_nthWildcard_not_referable() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **.R {",
" int id?;",
"}",
"-keep class <1>.D<2> {",
" int id<3>;", // Only ** and ? are referable wildcards.
"}"
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 6, 2,
"Wildcard", "<3>", "invalid");
}
verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (3,");
}
@Test
public void parse_if_nthWildcard_not_referable_after_backreference() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **.*User {",
" @<1>.*<2> <methods>;", // As back reference starts, * in the middle is not referable.
"}",
"-keep @interface <1>.<3><2>"
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 5, 1,
"Wildcard", "<3>", "invalid");
}
verifyFailWithProguard6(
proguardConfig, "Use of generics not allowed for java type at '<1>.<3><2>'");
}
@Test
public void parse_if_if() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$$ModuleAdapter",
"-if class **$$InjectAdapter"
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 1, 1,
"Expecting", "'-keep'", "after", "'-if'");
}
verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
}
@Test
public void parse_if_end() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$$ModuleAdapter"
);
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
fail();
} catch (AbortException e) {
checkDiagnostics(handler.errors, proguardConfig, 1, 1,
"Expecting", "'-keep'", "after", "'-if'");
}
verifyFailWithProguard6(proguardConfig, "Expecting '-keep' option after '-if' option");
}
@Test
public void parse_assumenoexternalsideeffects() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-assumenoexternalsideeffects class java.lang.StringBuilder {",
" public java.lang.StringBuilder();",
" public java.lang.StringBuilder(int);",
" public java.lang.StringBuilder(java.lang.String);",
" public java.lang.StringBuilder append(java.lang.Object);",
" public java.lang.StringBuilder append(java.lang.String);",
" public java.lang.StringBuilder append(java.lang.StringBuffer);",
" public java.lang.StringBuilder append(char[]);",
" public java.lang.StringBuilder append(char[], int, int);",
" public java.lang.StringBuilder append(boolean);",
" public java.lang.StringBuilder append(char);",
" public java.lang.StringBuilder append(int);",
" public java.lang.StringBuilder append(long);",
" public java.lang.StringBuilder append(float);",
" public java.lang.StringBuilder append(double);",
" public java.lang.String toString();",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoexternalsideeffects");
}
@Test
public void parse_assumenoescapingparameters() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-assumenoescapingparameters class java.lang.System {",
" public static void arraycopy(java.lang.Object, int, java.lang.Object, int, int);",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoescapingparameters");
}
@Test
public void parse_assumenoexternalreturnvalues() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-assumenoexternalreturnvalues class java.lang.StringBuilder {",
" public java.lang.StringBuilder append(java.lang.Object);",
" public java.lang.StringBuilder append(java.lang.String);",
" public java.lang.StringBuilder append(java.lang.StringBuffer);",
" public java.lang.StringBuilder append(char[]);",
" public java.lang.StringBuilder append(char[], int, int);",
" public java.lang.StringBuilder append(boolean);",
" public java.lang.StringBuilder append(char);",
" public java.lang.StringBuilder append(int);",
" public java.lang.StringBuilder append(long);",
" public java.lang.StringBuilder append(float);",
" public java.lang.StringBuilder append(double);",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-assumenoexternalreturnvalues");
}
@Test
public void parse_dump_withoutFile() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-dump"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-dump");
}
@Test
public void parse_dump_withFile() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-dump class_files.txt"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
"Ignoring", "-dump");
}
@Test
public void parse_android() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-android"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
}
@Test
public void parse_mergeinterfaceaggressively() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-mergeinterfacesaggressively"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
}
@Test
public void parse_addconfigurationdebugging() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-addconfigurationdebugging"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
assertTrue(parser.getConfig().isConfigurationDebugging());
}
@Test
public void parse_regress74508478() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-keep class A {",
" A <fields>;",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
checkDiagnostics(handler.warnings, proguardConfig, 2, 5, "The field name \"<fields>\" is");
verifyWithProguard(proguardConfig);
}
@Test
public void parse_regress79925760() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-keep public @ interface test.MyAnnotation"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardKeepRule rule = (ProguardKeepRule) config.getRules().get(0);
assertEquals(ProguardClassType.ANNOTATION_INTERFACE, rule.getClassType());
verifyWithProguard(proguardConfig);
}
@Test
public void parse_regress110021323() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-keepclassmembernames class A {",
" <public methods>;",
" <public fields>;",
"}"
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
assertEquals(4, handler.warnings.size());
checkDiagnostics(handler.warnings, 0, proguardConfig, 2, 3, "The type \"<public\" is");
checkDiagnostics(handler.warnings, 1, proguardConfig, 2, 11, "The field name \"methods>\" is");
checkDiagnostics(handler.warnings, 2, proguardConfig, 3, 3, "The type \"<public\" is");
checkDiagnostics(handler.warnings, 3, proguardConfig, 3, 11, "The field name \"fields>\" is");
verifyWithProguard(proguardConfig);
}
private void checkRulesSourceSnippet(List<String> sourceRules) {
checkRulesSourceSnippet(sourceRules, sourceRules, false);
}
private void checkRulesSourceSnippet(
List<String> sourceRules, List<String> expected, boolean trim) {
reset();
parser.parse(createConfigurationForTesting(sourceRules));
verifyParserEndsCleanly();
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(expected.size(), rules.size());
for (int i = 0; i < expected.size(); i++) {
assertEquals(trim ? expected.get(i).trim() : expected.get(i), rules.get(i).getSource());
}
}
@Test
public void accurateSourceSnippet() {
String rule1 = String.join(System.lineSeparator(), ImmutableList.of("-keep class A { *; }"));
String rule2 =
String.join(System.lineSeparator(), ImmutableList.of("-keep class A ", "{ *; ", "}"));
String rule3 =
String.join(
System.lineSeparator(), ImmutableList.of("-checkdiscard class A ", "{ *; ", "}"));
checkRulesSourceSnippet(ImmutableList.of(rule1));
checkRulesSourceSnippet(ImmutableList.of(rule2));
checkRulesSourceSnippet(ImmutableList.of(rule3));
checkRulesSourceSnippet(
ImmutableList.of(rule1, rule2, rule3), ImmutableList.of(rule1, rule3), false);
}
@Test
public void accurateSourceSnippet_withWhitespace() {
Iterable<String> nonEmptyWhiteSpace =
whiteSpace.stream().filter(space -> !space.equals("")).collect(Collectors.toList());
for (String space : nonEmptyWhiteSpace) {
String rule1 =
String.join(System.lineSeparator(), ImmutableList.of(" -keep class A { *; } "))
.replaceAll(" {2}", space);
String rule2 =
String.join(
System.lineSeparator(), ImmutableList.of("-keep class A ", "{ *; ", "}", ""))
.replaceAll(" {2}", space);
checkRulesSourceSnippet(ImmutableList.of(rule1), ImmutableList.of(rule1), true);
checkRulesSourceSnippet(
ImmutableList.of("#Test comment ", "", rule1), ImmutableList.of(rule1), true);
checkRulesSourceSnippet(
ImmutableList.of("#Test comment ", "", rule1, "", "#Test comment ", ""),
ImmutableList.of(rule1),
true);
checkRulesSourceSnippet(ImmutableList.of(rule2), ImmutableList.of(rule2), true);
checkRulesSourceSnippet(ImmutableList.of(rule1, rule2), ImmutableList.of(rule1), true);
checkRulesSourceSnippet(
ImmutableList.of(
"#Test comment ", "", rule1, " ", "#Test comment ", "", rule2, "#Test comment ", ""),
ImmutableList.of(rule1),
true);
}
}
@Test
public void parseOptionalArgumentsFollowedByArobaseInclude() throws Exception {
Path includeFile = writeTextToTempFile(
"-renamesourcefileattribute SRC"
);
Path proguardConfig = writeTextToTempFile(
"-printconfiguration",
"@" + includeFile.toAbsolutePath()
);
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertTrue(config.isPrintConfiguration());
assertNull(config.getPrintConfigurationFile());
assertEquals("SRC", config.getRenameSourceFileAttribute());
}
private void testFlagWithFilenames(
String flag,
List<String> values,
Function<ProguardConfiguration, Path> extractPath,
BiConsumer<String, String> check) {
for (String value : values) {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
check.accept(value, extractPath.apply(config).toString());
}
}
@Test
public void parseSingleFileNameOptions() {
List<String> fileNames = ImmutableList.of(
"xxx",
"xxx" + File.pathSeparatorChar + "xxx");
testFlagWithFilenames("-printusage", fileNames,
ProguardConfiguration::getPrintUsageFile, Assert::assertEquals);
testFlagWithFilenames("-printconfiguration", fileNames,
ProguardConfiguration::getPrintConfigurationFile, Assert::assertEquals);
testFlagWithFilenames("-printmapping", fileNames,
ProguardConfiguration::getPrintMappingFile, Assert::assertEquals);
testFlagWithFilenames("-applymapping", fileNames,
ProguardConfiguration::getApplyMappingFile, Assert::assertEquals);
// The parsed value of -basedirectory is not available in the configuration.
testFlagWithFilenames("-basedirectory", fileNames, (x) -> Paths.get(""), (x, y) -> {});
testFlagWithFilenames("-printseeds", fileNames,
ProguardConfiguration::getSeedFile, Assert::assertEquals);
// TODO(sgjesse): Add tests for -obfuscationdictionary, -classobfuscationdictionary,
// -packageobfuscationdictionary and -include
}
private void checkQuotedFileName(String quoted, String parsed) {
assertTrue(quoted.charAt(0) == '\'' || quoted.charAt(0) == '\"');
assertTrue(quoted.charAt(0) == quoted.charAt(quoted.length() - 1));
assertEquals(quoted.substring(1, quoted.length() - 1), parsed);
}
@Test
public void parseSingleFleNameOptionsQuoted() {
List<String> fileNames = new ArrayList<>();
fileNames.add("'xxx'");
fileNames.add("\"xxx\"");
fileNames.add("'xxx xxx\'");
fileNames.add("\"xxx xxx\"");
fileNames.add("\"'xxx'\"");
fileNames.add("\" xxx xxx (\"");
fileNames.add("' xxx xxx ('");
// Java Path implementation on Windows does not allow " or trailing
// spaces in file names.
if (!ToolHelper.isWindows()) {
fileNames.add("'\"xxx\"'");
fileNames.add("\" xxx xxx \"");
fileNames.add("' xxx xxx '");
}
testFlagWithFilenames("-printusage", fileNames,
ProguardConfiguration::getPrintUsageFile, this::checkQuotedFileName);
testFlagWithFilenames("-printconfiguration", fileNames,
ProguardConfiguration::getPrintConfigurationFile, this::checkQuotedFileName);
testFlagWithFilenames("-printmapping", fileNames,
ProguardConfiguration::getPrintMappingFile, this::checkQuotedFileName);
testFlagWithFilenames("-applymapping", fileNames,
ProguardConfiguration::getApplyMappingFile, this::checkQuotedFileName);
// The parsed value of -basedirectory is not available in the configuration.
testFlagWithFilenames("-basedirectory", fileNames, (x) -> Paths.get(""), (x, y) -> {});
testFlagWithFilenames("-printseeds", fileNames,
ProguardConfiguration::getSeedFile, this::checkQuotedFileName);
// TODO(sgjesse): Add tests for -obfuscationdictionary, -classobfuscationdictionary,
// -packageobfuscationdictionary and -include
}
private void testFlagWithFilenamesWithSystemProperty(
String flag,
List<String> values,
Function<ProguardConfiguration, Path> extractPath,
BiConsumer<String, String> check) {
for (String value : values) {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
Path path = extractPath.apply(config);
check.accept(
value
.replaceAll("<java.home>", Matcher.quoteReplacement(System.getProperty("java.home")))
.replaceAll("<user.home>", Matcher.quoteReplacement(System.getProperty("user.home"))),
path.toString());
}
}
@Test
public void parseFlagWithFilenamesWithSystemProperty() {
List<String> fileNames = new ArrayList<>();
fileNames.add("<java.home>");
fileNames.add("<user.home>");
if (!ToolHelper.isWindows()) {
// If the system property has e.g. C:\ prefix, then these file names
// will not work on Windows.
fileNames.add("xxx<java.home>");
fileNames.add("xxx<user.home>");
}
fileNames.add("<java.home>" + File.separatorChar + "xxx");
fileNames.add("<user.home>" + File.separatorChar + "xxx");
if (!ToolHelper.isWindows()) {
fileNames.add("xxx<java.home>/xxx");
fileNames.add("xxx<user.home>/xxx");
fileNames.add("<java.home><java.home>");
fileNames.add("<user.home><user.home>");
fileNames.add("<java.home><user.home>");
fileNames.add("<user.home><java.home>");
// The characters < and > are not allowed in paths on Windows.
fileNames.add(">");
fileNames.add("<");
fileNames.add("<<<<");
fileNames.add("><");
fileNames.add(">><<");
}
testFlagWithFilenamesWithSystemProperty("-printusage", fileNames,
ProguardConfiguration::getPrintUsageFile, Assert::assertEquals);
testFlagWithFilenamesWithSystemProperty("-printconfiguration", fileNames,
ProguardConfiguration::getPrintConfigurationFile, Assert::assertEquals);
testFlagWithFilenamesWithSystemProperty("-printmapping", fileNames,
ProguardConfiguration::getPrintMappingFile, Assert::assertEquals);
testFlagWithFilenamesWithSystemProperty("-applymapping", fileNames,
ProguardConfiguration::getApplyMappingFile, Assert::assertEquals);
// The parsed value of -basedirectory is not available in the configuration.
testFlagWithFilenamesWithSystemProperty("-basedirectory", fileNames,
(x) -> Paths.get(""), (x, y) -> {});
testFlagWithFilenamesWithSystemProperty("-printseeds", fileNames,
ProguardConfiguration::getSeedFile, Assert::assertEquals);
// TODO(sgjesse): Add tests for -obfuscationdictionary, -classobfuscationdictionary,
// -packageobfuscationdictionary and -include
}
@Test
public void pasteFlagWithFilenamesWithSystemProperty_empty() {
try {
parser.parse(createConfigurationForTesting(ImmutableList.of("-printusage <>")));
fail("Expect to fail due to the lack of file name.");
} catch (AbortException e) {
checkDiagnostics(handler.errors, null, 1, 15, "Value of system property '' not found");
}
}
@Test
public void pasteFlagWithFilenamesWithSystemProperty_notFound() {
// Find a non-existent system property.
String property = "x";
while (System.getProperty(property) != null) {
property = property + "x";
}
try {
parser.parse(
createConfigurationForTesting(ImmutableList.of("-printusage <" + property + ">")));
fail("Expect to fail due to the lack of file name.");
} catch (AbortException e) {
checkDiagnostics(
handler.errors, null, 1, 16, "Value of system property '" + property + "' not found");
}
}
private void testFlagWithQuotedValue(
String flag, String value, BiConsumer<PackageObfuscationMode, String> checker) {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
checker.accept(config.getPackageObfuscationMode(), config.getPackagePrefix());
}
private void testFlagWithQuotedValueFailure(
String flag, String value, Supplier<Void> checker) {
try {
reset();
parser.parse(createConfigurationForTesting(ImmutableList.of(flag + " " + value)));
fail("Expect to fail due to un-closed quote.");
} catch (AbortException e) {
checker.get();
}
}
@Test
public void parseRepackageclassesQuotes() {
testFlagWithQuotedValue("-repackageclasses", "'xxx'", (mode, prefix) -> {
assertEquals(PackageObfuscationMode.REPACKAGE, mode);
assertEquals("xxx", prefix);
});
testFlagWithQuotedValue("-repackageclasses", "\"xxx\"", (mode, prefix) -> {
assertEquals(PackageObfuscationMode.REPACKAGE, mode);
assertEquals("xxx", prefix);
});
testFlagWithQuotedValueFailure("-repackageclasses", "'xxx", () -> {
checkDiagnostics(handler.errors, null, 1, 23, "Missing closing quote");
return null;
});
testFlagWithQuotedValueFailure("-repackageclasses", "'xxx\"", () -> {
checkDiagnostics(handler.errors, null, 1, 23, "Missing closing quote");
return null;
});
testFlagWithQuotedValueFailure("-repackageclasses", "\"xxx", () -> {
checkDiagnostics(handler.errors, null, 1, 23, "Missing closing quote");
return null;
});
testFlagWithQuotedValueFailure("-repackageclasses", "\"xxx'", () -> {
checkDiagnostics(handler.errors, null, 1, 23, "Missing closing quote");
return null;
});
}
@Test
public void parseFlattenpackagehierarchyQuotes() {
testFlagWithQuotedValue("-flattenpackagehierarchy", "'xxx'", (mode, prefix) -> {
assertEquals(PackageObfuscationMode.FLATTEN, mode);
assertEquals("xxx", prefix);
});
testFlagWithQuotedValue("-flattenpackagehierarchy", "\"xxx\"", (mode, prefix) -> {
assertEquals(PackageObfuscationMode.FLATTEN, mode);
assertEquals("xxx", prefix);
});
testFlagWithQuotedValueFailure("-flattenpackagehierarchy", "'xxx", () -> {
checkDiagnostics(handler.errors, null, 1, 30, "Missing closing quote");
return null;
});
testFlagWithQuotedValueFailure("-flattenpackagehierarchy", "'xxx\"", () -> {
checkDiagnostics(handler.errors, null, 1, 30, "Missing closing quote");
return null;
});
testFlagWithQuotedValueFailure("-flattenpackagehierarchy", "\"xxx", () -> {
checkDiagnostics(handler.errors, null, 1, 30, "Missing closing quote");
return null;
});
testFlagWithQuotedValueFailure("-flattenpackagehierarchy", "\"xxx'", () -> {
checkDiagnostics(handler.errors, null, 1, 30, "Missing closing quote");
return null;
});
}
private ProguardConfiguration parseAndVerifyParserEndsCleanly(List<String> config) {
parser.parse(createConfigurationForTesting(config));
verifyParserEndsCleanly();
return parser.getConfig();
}
private ProguardConfiguration parseAndVerifyParserEndsCleanly(Path config) {
parser.parse(config);
verifyParserEndsCleanly();
return parser.getConfig();
}
private void verifyParserEndsCleanly() {
assertEquals(0, handler.infos.size());
assertEquals(0, handler.warnings.size());
assertEquals(0, handler.errors.size());
}
private void verifyWithProguard(Path proguardConfig) throws Exception {
if (isRunProguard()) {
// Add a keep rule for the test class as Proguard will fail if the resulting output jar is
// empty
Class classToKeepForTest = EmptyMainClassForProguardTests.class;
Path additionalProguardConfig = writeTextToTempFile(
"-keep class " + classToKeepForTest.getCanonicalName() + " {",
" public static void main(java.lang.String[]);",
"}"
);
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
ProcessResult result = ToolHelper.runProguardRaw(
jarTestClasses(ImmutableList.of(classToKeepForTest)),
proguardedJar,
ImmutableList.of(proguardConfig, additionalProguardConfig),
null);
assertEquals(0, result.exitCode);
CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar));
assertEquals(1, proguardInspector.allClasses().size());
}
}
private void verifyWithProguard6(Path proguardConfig) throws Exception {
if (isRunProguard()) {
// Add a keep rule for the test class as Proguard will fail if the resulting output jar is
// empty
Class classToKeepForTest = EmptyMainClassForProguardTests.class;
Path additionalProguardConfig = writeTextToTempFile(
"-keep class " + classToKeepForTest.getCanonicalName() + " {",
" public static void main(java.lang.String[]);",
"}"
);
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
ProcessResult result = ToolHelper.runProguard6Raw(
jarTestClasses(ImmutableList.of(classToKeepForTest)),
proguardedJar,
ImmutableList.of(proguardConfig, additionalProguardConfig),
null);
assertEquals(0, result.exitCode);
CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar));
assertEquals(1, proguardInspector.allClasses().size());
}
}
private void verifyFailWithProguard6(Path proguardConfig, String expectedMessage)
throws Exception{
if (isRunProguard()) {
// No need for a keep rule for this class, as we are expecting Proguard to fail with the
// specified message.
Class classForTest = EmptyMainClassForProguardTests.class;
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
ProcessResult result = ToolHelper.runProguard6Raw(
jarTestClasses(ImmutableList.of(classForTest)), proguardedJar, proguardConfig, null);
assertNotEquals(0, result.exitCode);
assertThat(result.stderr, containsString(expectedMessage));
}
}
@Test
public void b124181032() {
// Test spaces and quotes in class name list.
ProguardConfigurationParser parser;
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(
createConfigurationForTesting(
ImmutableList.of(
"-keepclassmembers class \"a.b.c.**\" ,"
+ " !**d , '!**e' , \"!**f\" , g , 'h' , \"i\" { ",
"<fields>;",
"<init>();",
"}")));
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS.toString(), rule.typeString());
assertEquals("a.b.c.**,!**d,!**e,!**f,g,h,i", rule.getClassNames().toString());
}
@Test
public void directiveAfterRepackagingRuleTest() {
List<PackageObfuscationMode> packageObfuscationModes =
ImmutableList.of(PackageObfuscationMode.FLATTEN, PackageObfuscationMode.REPACKAGE);
for (PackageObfuscationMode packageObfuscationMode : packageObfuscationModes) {
String directive =
packageObfuscationMode == PackageObfuscationMode.FLATTEN
? "-flattenpackagehierarchy"
: "-repackageclasses";
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(directive + " -keep class *")));
ProguardConfiguration configuration = parser.getConfig();
assertEquals(packageObfuscationMode, configuration.getPackageObfuscationMode());
assertEquals("", configuration.getPackagePrefix());
List<ProguardConfigurationRule> rules = configuration.getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
assertEquals(ProguardKeepRuleType.KEEP.toString(), rule.typeString());
assertEquals("*", rule.getClassNames().toString());
}
}
@Test
public void arobaseAfterRepackagingRuleTest() throws IOException {
Path includeFile = writeTextToTempFile("-keep class *");
List<PackageObfuscationMode> packageObfuscationModes =
ImmutableList.of(PackageObfuscationMode.FLATTEN, PackageObfuscationMode.REPACKAGE);
for (PackageObfuscationMode packageObfuscationMode : packageObfuscationModes) {
String directive =
packageObfuscationMode == PackageObfuscationMode.FLATTEN
? "-flattenpackagehierarchy"
: "-repackageclasses";
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(
createConfigurationForTesting(
ImmutableList.of(directive + " @" + includeFile.toAbsolutePath())));
ProguardConfiguration configuration = parser.getConfig();
assertEquals(packageObfuscationMode, configuration.getPackageObfuscationMode());
assertEquals("", configuration.getPackagePrefix());
List<ProguardConfigurationRule> rules = configuration.getRules();
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
assertEquals(ProguardKeepRuleType.KEEP.toString(), rule.typeString());
assertEquals("*", rule.getClassNames().toString());
}
}
@Test
public void negatedMemberTypeTest() throws IOException {
Path proguardConfigurationFile = writeTextToTempFile("-keepclassmembers class ** { !int x; }");
try {
new ProguardConfigurationParser(new DexItemFactory(), reporter)
.parse(proguardConfigurationFile);
fail("Expected to fail since the type name cannot be negated.");
} catch (AbortException e) {
checkDiagnostics(
handler.errors,
proguardConfigurationFile,
1,
30,
"Unexpected character '!': "
+ "The negation character can only be used to negate access flags");
}
}
@Test
public void parseInits() throws Exception {
for (String initName : InitMatchingTest.ALLOWED_INIT_NAMES) {
reset();
Path initConfig = writeTextToTempFile(
"-keep class **.MyClass {",
" " + initName + "(...);",
"}");
parseAndVerifyParserEndsCleanly(initConfig);
}
for (String initName : InitMatchingTest.INIT_NAMES) {
// Tested above.
if (InitMatchingTest.ALLOWED_INIT_NAMES.contains(initName)) {
continue;
}
reset();
Path proguardConfig = writeTextToTempFile(
"-keep class **.MyClass {",
" " + initName + "(...);",
"}");
try {
parser.parse(proguardConfig);
fail("Expect to fail due to unsupported constructor name pattern.");
} catch (AbortException e) {
int column = initName.contains("void") ? initName.indexOf("void") + 8
: (initName.contains("XYZ") ? initName.indexOf(">") + 4 : 3);
if (initName.contains("XYZ")) {
checkDiagnostics(
handler.errors, proguardConfig, 2, column, "Expected [access-flag]* void ");
} else {
checkDiagnostics(
handler.errors, proguardConfig, 2, column, "Unexpected character", "method name");
}
}
// For some exceptional cases, Proguard accepts the rules but fails with an empty jar message.
if (initName.contains("<init>")
|| initName.contains("<clinit>")
|| initName.contains("void")) {
continue;
}
verifyFailWithProguard6(
proguardConfig, "Expecting type and name instead of just '" + initName + "'");
}
}
@Test
public void parseIncludeCode() throws Exception {
ProguardConfigurationParser parser;
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path proguardConfig = writeTextToTempFile("-keep,includecode class A { method(); }");
parser.parse(proguardConfig);
assertEquals(1, parser.getConfig().getRules().size());
assertEquals(1, handler.infos.size());
checkDiagnostics(handler.infos, proguardConfig, 1, 7, "Ignoring modifier", "includecode");
}
@Test
public void parseFileStartingWithBOM() throws Exception {
// Copied from test 'parseIncludeCode()' and added a BOM.
ProguardConfigurationParser parser;
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path proguardConfig =
writeTextToTempFile(StringUtils.BOM + "-keep,includecode class A { method(); }");
byte[] bytes = Files.readAllBytes(proguardConfig);
assertEquals(0xef, Byte.toUnsignedLong(bytes[0]));
assertEquals(0xbb, Byte.toUnsignedLong(bytes[1]));
assertEquals(0xbf, Byte.toUnsignedLong(bytes[2]));
parser.parse(proguardConfig);
assertEquals(1, parser.getConfig().getRules().size());
assertEquals(1, handler.infos.size());
checkDiagnostics(handler.infos, proguardConfig, 1, 7, "Ignoring modifier", "includecode");
}
@Test
public void parseFileWithLotsOfWhitespace() throws Exception {
List<String> ws =
ImmutableList.of(
"",
" ",
" ",
"\t ",
" \t",
"" + StringUtils.BOM,
StringUtils.BOM + " " + StringUtils.BOM);
// Copied from test 'parseIncludeCode()' and added whitespace.
for (String whitespace1 : ws) {
for (String whitespace2 : ws) {
reset();
Path proguardConfig =
writeTextToTempFile(
whitespace1
+ "-keep,includecode "
+ whitespace2
+ "class A { method(); }"
+ whitespace1);
parser.parse(proguardConfig);
assertEquals(1, parser.getConfig().getRules().size());
assertEquals(1, handler.infos.size());
// All BOSs except the leading are counted as input characters.
checkDiagnostics(handler.infos, proguardConfig, 1, 0, "Ignoring modifier", "includecode");
}
}
}
@Test
public void backReferenceElimination() {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser = new ProguardConfigurationParser(dexItemFactory, reporter);
String configuration = StringUtils.lines("-if class *.*.*", "-keep class <1>.<2>$<3>");
parser.parse(createConfigurationForTesting(ImmutableList.of(configuration)));
verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardIfRule ifRule = (ProguardIfRule) config.getRules().iterator().next();
// Evaluate the class name matcher against foo.bar.Baz.
DexType type = dexItemFactory.createType("Lfoo/bar/Baz;");
ifRule.getClassNames().matches(type);
// Materialize the subsequent rule.
ProguardKeepRule materializedSubsequentRule = ifRule.subsequentRule.materialize(dexItemFactory);
// Verify that the class name matcher of the materialized rule has a specific type.
ProguardClassNameList classNameList = materializedSubsequentRule.getClassNames();
assertTrue(classNameList instanceof SingleClassNameList);
SingleClassNameList singleClassNameList = (SingleClassNameList) classNameList;
assertTrue(singleClassNameList.className instanceof MatchSpecificType);
MatchSpecificType specificTypeMatcher = (MatchSpecificType) singleClassNameList.className;
assertEquals("foo.bar$Baz", specificTypeMatcher.type.toSourceString());
}
@Test
public void parseCheckenumstringsdiscarded() throws Exception {
Path proguardConfig =
writeTextToTempFile("-checkenumstringsdiscarded @com.example.SomeAnnotation enum *");
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
verifyParserEndsCleanly();
}
}