| // 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 com.android.tools.r8.utils.BooleanUtils.intValue; | |
| import static org.junit.Assert.assertEquals; | |
| import static org.junit.Assert.assertFalse; | |
| 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.TestParameters; | |
| import com.android.tools.r8.TestParametersCollection; | |
| import com.android.tools.r8.ToolHelper; | |
| import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration; | |
| 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.IterableUtils; | |
| import com.android.tools.r8.utils.KeepingDiagnosticHandler; | |
| import com.android.tools.r8.utils.ListUtils; | |
| import com.android.tools.r8.utils.Reporter; | |
| import com.android.tools.r8.utils.StringUtils; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| 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.Iterator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Optional; | |
| import java.util.Set; | |
| import java.util.function.BiConsumer; | |
| import java.util.function.Function; | |
| import java.util.function.Predicate; | |
| 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; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.Parameterized; | |
| class EmptyMainClassForProguardTests { | |
| public static void main(String[] args) { | |
| } | |
| } | |
| @RunWith(Parameterized.class) | |
| public class ProguardConfigurationParserTest extends TestBase { | |
| private static final String VALID_PROGUARD_DIR = ToolHelper.TESTS_DIR + "proguard/valid/"; | |
| private static final String INVALID_PROGUARD_DIR = ToolHelper.TESTS_DIR + "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 ADAPT_KOTLIN_METADATA = | |
| VALID_PROGUARD_DIR + "adapt-kotlin-metadata.flags"; | |
| private static final String KEEP_KOTLIN_METADATA = | |
| VALID_PROGUARD_DIR + "keep-kotlin-metadata.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('"', '\''); | |
| private ProguardConfiguration.Builder builder; | |
| private DexItemFactory dexItemFactory; | |
| @Parameterized.Parameters(name = "{0}") | |
| public static TestParametersCollection data() { | |
| return getTestParameters().withNoneRuntime().build(); | |
| } | |
| public ProguardConfigurationParserTest(TestParameters parameters) { | |
| parameters.assertNoneRuntime(); | |
| } | |
| @Before | |
| public void reset() { | |
| handler = new KeepingDiagnosticHandler(); | |
| reporter = new Reporter(handler); | |
| dexItemFactory = new DexItemFactory(); | |
| builder = ProguardConfiguration.builder(dexItemFactory, reporter); | |
| parser = | |
| new ProguardConfigurationParser( | |
| dexItemFactory, | |
| reporter, | |
| ProguardConfigurationParserOptions.builder() | |
| .setEnableLegacyFullModeForKeepRules(false) | |
| .setEnableTestingOptions(false) | |
| .build(), | |
| null, | |
| builder); | |
| } | |
| @Before | |
| public void resetAllowTestOptions() { | |
| handler = new KeepingDiagnosticHandler(); | |
| reporter = new Reporter(handler); | |
| dexItemFactory = new DexItemFactory(); | |
| builder = ProguardConfiguration.builder(dexItemFactory, reporter); | |
| parser = | |
| new ProguardConfigurationParser( | |
| dexItemFactory, | |
| reporter, | |
| ProguardConfigurationParserOptions.builder() | |
| .setEnableTestingOptions(true) | |
| .build(), | |
| null, | |
| builder); | |
| } | |
| @Test | |
| public void parse() throws Exception { | |
| // Parse from file. | |
| parser.parse(Paths.get(PROGUARD_SPEC_FILE)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> rules = builder.build().getRules(); | |
| assertEquals(24, rules.size()); | |
| assertEquals(1, rules.get(0).getMemberRules().size()); | |
| // Parse from strings. | |
| String lines = FileUtils.readTextFile(Paths.get(PROGUARD_SPEC_FILE)); | |
| parser.parse(createConfigurationForTesting(lines)); | |
| rules = builder.build().getRules(); | |
| assertEquals(24, rules.size()); | |
| assertEquals(1, rules.get(0).getMemberRules().size()); | |
| } | |
| @Test | |
| public void parseMultipleNamePatterns() { | |
| parser.parse(Paths.get(MULTIPLE_NAME_PATTERNS_FILE)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> rules = builder.build().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() { | |
| String nonJavaIdentifiers = | |
| String.join("\n", ImmutableList.of( | |
| "-keep class -package-.-ClassNameWithDash-{", | |
| " -NameWithDash- -field-;", | |
| " p-.-OtherNameWithDash- -method-(-p.-WithDash-, -package-.-ClassNameWithDash-[]); ", | |
| "}")); | |
| parser.parse(createConfigurationForTesting(nonJavaIdentifiers)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> rules = builder.build().getRules(); | |
| assertEquals(1, rules.size()); | |
| assertEquals(ProguardClassType.CLASS, rules.get(0).getClassType()); | |
| assertEquals(1, rules.get(0).getClassNames().size()); | |
| Set<DexType> classTypes = rules.get(0).getClassNames().getSpecificTypes(); | |
| assertEquals(1, classTypes.size()); | |
| assertSame( | |
| dexItemFactory.createType("L-package-/-ClassNameWithDash-;"), | |
| IterableUtils.first(classTypes)); | |
| 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, Predicate<DexType>> matcherFactory) { | |
| String configuration = "-dont" + xxx + " !foobar,*bar"; | |
| parser.parse(createConfigurationForTesting(configuration)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| Predicate<DexType> matcher = matcherFactory.apply(config); | |
| assertFalse(matcher.test(dexItemFactory.createType("Lboobaz;"))); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lboobar;"))); | |
| assertFalse(matcher.test(dexItemFactory.createType("Lfoobar;"))); | |
| } | |
| @Test | |
| public void testDontXXX() { | |
| testDontXXX("warn", config -> DontWarnConfiguration.create(config)::matches); | |
| testDontXXX("note", config -> config.getDontNotePatterns()::matches); | |
| } | |
| private void testDontXXXMultiple( | |
| String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) { | |
| DexItemFactory dexItemFactory = new DexItemFactory(); | |
| String configuration1 = StringUtils.lines("-dont" + xxx + " foo.**, bar.**"); | |
| String configuration2 = StringUtils.lines("-dont" + xxx + " foo.**", "-dontwarn bar.**"); | |
| for (String configuration : ImmutableList.of(configuration1, configuration2)) { | |
| parser.parse(createConfigurationForTesting(configuration)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| Predicate<DexType> matcher = matcherFactory.apply(config); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lfoo/Bar;"))); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lfoo/bar7Bar;"))); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lbar/Foo;"))); | |
| } | |
| } | |
| @Test | |
| public void testDontWarnMultiple() { | |
| testDontXXXMultiple("warn", config -> DontWarnConfiguration.create(config)::matches); | |
| testDontXXXMultiple("note", config -> config.getDontNotePatterns()::matches); | |
| } | |
| private void testDontXXXAllExplicitly( | |
| String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) { | |
| DexItemFactory dexItemFactory = new DexItemFactory(); | |
| String dontwarnAll = "-dont" + xxx + " *"; | |
| parser.parse(createConfigurationForTesting(dontwarnAll)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| Predicate<DexType> matcher = matcherFactory.apply(config); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lboobaz;"))); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lboobar;"))); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lfoobar;"))); | |
| } | |
| @Test | |
| public void testDontWarnAllExplicitly() { | |
| testDontXXXAllExplicitly("warn", config -> DontWarnConfiguration.create(config)::matches); | |
| testDontXXXAllExplicitly("note", config -> config.getDontNotePatterns()::matches); | |
| } | |
| private void testDontXXXAllImplicitly( | |
| String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) { | |
| DexItemFactory dexItemFactory = new DexItemFactory(); | |
| String dontwarnAll = "-dont" + xxx; | |
| String otherOption = "-keep class *"; | |
| parser.parse(createConfigurationForTesting(StringUtils.lines(dontwarnAll, otherOption))); | |
| ProguardConfiguration config = builder.build(); | |
| Predicate<DexType> matcher = matcherFactory.apply(config); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lboobaz;"))); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lboobar;"))); | |
| assertTrue(matcher.test(dexItemFactory.createType("Lfoobar;"))); | |
| } | |
| @Test | |
| public void testDontWarnAllImplicitly() { | |
| testDontXXXAllImplicitly("warn", config -> DontWarnConfiguration.create(config)::matches); | |
| testDontXXXAllImplicitly("note", config -> config.getDontNotePatterns()::matches); | |
| } | |
| @Test | |
| public void parseAccessFlags() { | |
| parser.parse(Paths.get(ACCESS_FLAGS_FILE)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> rules = builder.build().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() { | |
| parser.parse(Paths.get(WHY_ARE_YOU_KEEPING_FILE)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> rules = builder.build().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() { | |
| parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> assumeNoSideEffects = builder.build().getRules(); | |
| assertEquals(1, assumeNoSideEffects.size()); | |
| assumeNoSideEffects.get(0).getMemberRules().forEach(rule -> { | |
| assertFalse(rule.hasReturnValue()); | |
| }); | |
| } | |
| @Test | |
| public void parseAssumeNoSideEffectsWithReturnValue() { | |
| parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS_WITH_RETURN_VALUE)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> assumeNoSideEffects = builder.build().getRules(); | |
| assertEquals(1, assumeNoSideEffects.size()); | |
| int matches = 0; | |
| for (ProguardMemberRule rule : assumeNoSideEffects.get(0).getMemberRules()) { | |
| if (rule.getName().matches("returnsTrue") || rule.getName().matches("returnsFalse")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertTrue(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals(rule.getName().matches("returnsTrue"), rule.getReturnValue().getBoolean()); | |
| matches |= 1 << 0; | |
| } else if (rule.getName().matches("returns1")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertTrue(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals(1, rule.getReturnValue().getValueRange().getMin()); | |
| assertEquals(1, rule.getReturnValue().getValueRange().getMax()); | |
| matches |= 1 << 1; | |
| } else if (rule.getName().matches("returns2To4")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertTrue(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals(2, rule.getReturnValue().getValueRange().getMin()); | |
| assertEquals(4, rule.getReturnValue().getValueRange().getMax()); | |
| matches |= 1 << 2; | |
| } else if (rule.getName().matches("returns234To567")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertTrue(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals(234, rule.getReturnValue().getValueRange().getMin()); | |
| assertEquals(567, rule.getReturnValue().getValueRange().getMax()); | |
| matches |= 1 << 3; | |
| } else if (rule.getName().matches("returnsField")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertTrue(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName()); | |
| assertEquals("X", rule.getReturnValue().getFieldName().toString()); | |
| matches |= 1 << 4; | |
| } else if (rule.getName().matches("returnsNull")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertTrue(rule.getReturnValue().isNullability()); | |
| assertTrue(rule.getReturnValue().getNullability().isDefinitelyNull()); | |
| matches |= 1 << 5; | |
| } else if (rule.getName().matches("returnsNonNull")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertTrue(rule.getReturnValue().isNullability()); | |
| assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull()); | |
| matches |= 1 << 6; | |
| } else if (rule.getName().matches("returnsNonNullField")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertTrue(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName()); | |
| assertEquals("X", rule.getReturnValue().getFieldName().toString()); | |
| matches |= 1 << 7; | |
| } else { | |
| fail("Unexpected"); | |
| } | |
| } | |
| assertEquals((1 << 8) - 1, matches); | |
| } | |
| @Test | |
| public void parseAssumeValuesWithReturnValue() { | |
| parser.parse(Paths.get(ASSUME_VALUES_WITH_RETURN_VALUE)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> assumeValues = builder.build().getRules(); | |
| assertEquals(1, assumeValues.size()); | |
| int matches = 0; | |
| for (ProguardMemberRule rule : assumeValues.get(0).getMemberRules()) { | |
| if (rule.getName().matches("isTrue") || rule.getName().matches("isFalse")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertTrue(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals(rule.getName().matches("isTrue"), rule.getReturnValue().getBoolean()); | |
| matches |= 1 << 0; | |
| } else if (rule.getName().matches("is1")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertTrue(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals(1, rule.getReturnValue().getValueRange().getMin()); | |
| assertEquals(1, rule.getReturnValue().getValueRange().getMax()); | |
| matches |= 1 << 1; | |
| } else if (rule.getName().matches("is2To4")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertTrue(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals(2, rule.getReturnValue().getValueRange().getMin()); | |
| assertEquals(4, rule.getReturnValue().getValueRange().getMax()); | |
| matches |= 1 << 2; | |
| } else if (rule.getName().matches("is234To567")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertTrue(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertEquals(234, rule.getReturnValue().getValueRange().getMin()); | |
| assertEquals(567, rule.getReturnValue().getValueRange().getMax()); | |
| matches |= 1 << 3; | |
| } else if (rule.getName().matches("isField")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertTrue(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertTrue(rule.getReturnValue().getNullability().isMaybeNull()); | |
| assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName()); | |
| assertEquals("X", rule.getReturnValue().getFieldName().toString()); | |
| matches |= 1 << 4; | |
| } else if (rule.getName().matches("isNull")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertTrue(rule.getReturnValue().isNullability()); | |
| assertTrue(rule.getReturnValue().getNullability().isDefinitelyNull()); | |
| matches |= 1 << 5; | |
| } else if (rule.getName().matches("returnsNonNull")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertFalse(rule.getReturnValue().isField()); | |
| assertTrue(rule.getReturnValue().isNullability()); | |
| assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull()); | |
| matches |= 1 << 6; | |
| } else if (rule.getName().matches("returnsNonNullField")) { | |
| assertTrue(rule.hasReturnValue()); | |
| assertFalse(rule.getReturnValue().isBoolean()); | |
| assertFalse(rule.getReturnValue().isValueRange()); | |
| assertTrue(rule.getReturnValue().isField()); | |
| assertFalse(rule.getReturnValue().isNullability()); | |
| assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull()); | |
| assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName()); | |
| assertEquals("X", rule.getReturnValue().getFieldName().toString()); | |
| matches |= 1 << 7; | |
| } else { | |
| fail("Unexpected"); | |
| } | |
| } | |
| assertEquals((1 << 8) - 1, matches); | |
| } | |
| @Test | |
| public void testAdaptClassStrings() { | |
| String adaptClassStrings = "-adaptclassstrings !foobar,*bar"; | |
| parser.parse(createConfigurationForTesting(adaptClassStrings)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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() { | |
| String configuration1 = "-adaptclassstrings foo.**, bar.**"; | |
| String configuration2 = | |
| StringUtils.lines("-adaptclassstrings foo.**", "-adaptclassstrings bar.**"); | |
| for (String configuration : ImmutableList.of(configuration1, configuration2)) { | |
| parser.parse(createConfigurationForTesting(configuration)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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() { | |
| String adaptAll = "-adaptclassstrings *"; | |
| parser.parse(createConfigurationForTesting(adaptAll)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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() { | |
| String adaptAll = "-adaptclassstrings"; | |
| parser.parse(createConfigurationForTesting(adaptAll)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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() { | |
| 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(StringUtils.lines(config1, config2, config3))); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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()); | |
| assertEquals(1, memberRule.getAnnotations().size()); | |
| assertTrue( | |
| ListUtils.first(memberRule.getAnnotations()) | |
| .toString() | |
| .endsWith("IdentifierNameString")); | |
| }); | |
| } | |
| @Test | |
| public void testConvertCheckNotNullWithReturn() { | |
| ProguardConfiguration.Builder builder = ProguardConfiguration.builder(dexItemFactory, reporter); | |
| ProguardConfigurationParser parser = | |
| new ProguardConfigurationParser( | |
| dexItemFactory, | |
| reporter, | |
| ProguardConfigurationParserOptions.builder() | |
| .setEnableTestingOptions(false) | |
| .build(), | |
| null, | |
| builder); | |
| String rule = "-convertchecknotnull class C { ** m(**, ...); }"; | |
| parser.parse(createConfigurationForTesting(rule)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(1, config.getRules().size()); | |
| assertTrue(config.getRules().get(0) instanceof ConvertCheckNotNullRule); | |
| } | |
| @Test | |
| public void testConvertCheckNotNullWithoutReturn() { | |
| ProguardConfigurationParser parser = | |
| new ProguardConfigurationParser( | |
| dexItemFactory, | |
| reporter, | |
| ProguardConfigurationParserOptions.builder() | |
| .setEnableTestingOptions(false) | |
| .build(), | |
| null, | |
| builder); | |
| String rule = "-convertchecknotnull class C { void m(**, ...); }"; | |
| parser.parse(createConfigurationForTesting(rule)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(1, config.getRules().size()); | |
| assertTrue(config.getRules().get(0) instanceof ConvertCheckNotNullRule); | |
| } | |
| @Test | |
| public void parseDontobfuscate() { | |
| parser.parse(Paths.get(DONT_OBFUSCATE)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertFalse(config.isObfuscating()); | |
| } | |
| @Test | |
| public void parseRepackageClassesEmpty() { | |
| parser.parse(Paths.get(PACKAGE_OBFUSCATION_1)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode()); | |
| assertNotNull(config.getPackagePrefix()); | |
| assertEquals("", config.getPackagePrefix()); | |
| } | |
| @Test | |
| public void parseRepackageClassesNonEmpty() { | |
| parser.parse(Paths.get(PACKAGE_OBFUSCATION_2)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode()); | |
| assertNotNull(config.getPackagePrefix()); | |
| assertEquals("p.q.r", config.getPackagePrefix()); | |
| } | |
| @Test | |
| public void parseFlattenPackageHierarchyEmpty() { | |
| parser.parse(Paths.get(PACKAGE_OBFUSCATION_3)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode()); | |
| assertNotNull(config.getPackagePrefix()); | |
| assertEquals("", config.getPackagePrefix()); | |
| } | |
| @Test | |
| public void parseFlattenPackageHierarchyNonEmpty() { | |
| parser.parse(Paths.get(PACKAGE_OBFUSCATION_4)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(PackageObfuscationMode.FLATTEN, config.getPackageObfuscationMode()); | |
| assertNotNull(config.getPackagePrefix()); | |
| assertEquals("p.q.r", config.getPackagePrefix()); | |
| } | |
| @Test | |
| public void flattenPackageHierarchyCannotOverrideRepackageClasses() { | |
| Path path = Paths.get(PACKAGE_OBFUSCATION_5); | |
| parser.parse(path); | |
| checkDiagnostics(handler.warnings, path, 6, 1, | |
| "repackageclasses", "overrides", "flattenpackagehierarchy"); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode()); | |
| assertNotNull(config.getPackagePrefix()); | |
| assertEquals("top", config.getPackagePrefix()); | |
| } | |
| @Test | |
| public void repackageClassesOverridesFlattenPackageHierarchy() { | |
| Path path = Paths.get(PACKAGE_OBFUSCATION_6); | |
| parser.parse(path); | |
| checkDiagnostics(handler.warnings, path, 6, 1, | |
| "repackageclasses", "overrides", "flattenpackagehierarchy"); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(PackageObfuscationMode.REPACKAGE, config.getPackageObfuscationMode()); | |
| assertNotNull(config.getPackagePrefix()); | |
| assertEquals("top", config.getPackagePrefix()); | |
| } | |
| @Test | |
| public void parseApplyMapping() { | |
| parser.parse(Paths.get(APPLY_MAPPING)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertTrue(config.hasApplyMappingFile()); | |
| } | |
| @Test | |
| public void parseApplyMappingWithoutFile() { | |
| Path path = Paths.get(APPLY_MAPPING_WITHOUT_FILE); | |
| try { | |
| parser.parse(path); | |
| fail("Expect to fail due to the lack of file name."); | |
| } catch (RuntimeException e) { | |
| checkDiagnostics(handler.errors, path, 6, 14, "File name expected"); | |
| } | |
| } | |
| @Test | |
| public void parseAdaptKotlinMetadata() { | |
| Path path = Paths.get(ADAPT_KOTLIN_METADATA); | |
| parser.parse(path); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseKeepKotlinMetadata() { | |
| Path path = Paths.get(KEEP_KOTLIN_METADATA); | |
| parser.parse(path); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals( | |
| "-keepattributes RuntimeVisibleAnnotations", config.getKeepAttributes().toString()); | |
| assertEquals( | |
| StringUtils.joinLines("-keep class kotlin.Metadata {", " *;", "}"), | |
| config.getRules().get(0).toString()); | |
| } | |
| @Test | |
| public void parseIncluding() { | |
| parser.parse(Paths.get(INCLUDING)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseInvalidIncluding1() { | |
| Path path = Paths.get(INVALID_INCLUDING_1); | |
| try { | |
| parser.parse(path); | |
| fail(); | |
| } catch (RuntimeException e) { | |
| checkDiagnostics(handler.errors, path, 6, 1, "does-not-exist.flags"); | |
| } | |
| } | |
| @Test | |
| public void parseInvalidIncluding2() { | |
| Path path = Paths.get(INVALID_INCLUDING_2); | |
| try { | |
| parser.parse(path); | |
| fail(); | |
| } catch (RuntimeException e) { | |
| checkDiagnostics(handler.errors, path, 6, 1, "does-not-exist.flags"); | |
| } | |
| } | |
| @Test | |
| public void parseLibraryJars() { | |
| if (!ToolHelper.isLinux() && !ToolHelper.isMac()) { | |
| parser.parse(Paths.get(LIBRARY_JARS_WIN)); | |
| } else { | |
| parser.parse(Paths.get(LIBRARY_JARS)); | |
| } | |
| assertEquals(4, builder.build().getLibraryjars().size()); | |
| } | |
| @Test | |
| public void parseInvalidFilePattern() { | |
| try { | |
| parser.parse(createConfigurationForTesting("-injars abc.jar(*.zip;*.class)")); | |
| fail(); | |
| } catch (RuntimeException e) { | |
| assertEquals(1, handler.errors.size()); | |
| } | |
| } | |
| @Test | |
| public void parseKeepModifiers() { | |
| for (String before : whiteSpace) { | |
| for (String after : whiteSpace) { | |
| reset(); | |
| parseAndVerifyParserEndsCleanly( | |
| "-keep" | |
| + before | |
| + "," | |
| + after | |
| + "includedescriptorclasses" | |
| + before | |
| + "," | |
| + after | |
| + "allowaccessmodification" | |
| + before | |
| + "," | |
| + after | |
| + "allowshrinking" | |
| + before | |
| + "," | |
| + after | |
| + "allowobfuscation" | |
| + before | |
| + "," | |
| + after | |
| + "allowoptimization " | |
| + "class A { *; }"); | |
| } | |
| } | |
| } | |
| @Test | |
| public void parseKeepAnnotation() { | |
| for (String space : whiteSpace) { | |
| reset(); | |
| parseAndVerifyParserEndsCleanly("-keep @" + space + "interface A"); | |
| } | |
| } | |
| @Test | |
| public void regress78442725() { | |
| parseAndVerifyParserEndsCleanly( | |
| StringUtils.lines( | |
| "-keep, includedescriptorclasses class in.uncod.android.bypass.Document { *; }", | |
| "-keep, includedescriptorclasses class in.uncod.android.bypass.Element { *; }")); | |
| } | |
| @Test | |
| public void parseSeeds() { | |
| parser.parse(Paths.get(SEEDS)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertTrue(config.isPrintSeeds()); | |
| assertNull(config.getSeedFile()); | |
| } | |
| @Test | |
| public void parseSeeds2() { | |
| parser.parse(Paths.get(SEEDS_2)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertTrue(config.isPrintSeeds()); | |
| assertNotNull(config.getSeedFile()); | |
| } | |
| @Test | |
| public void parseVerbose() { | |
| parser.parse(Paths.get(VERBOSE)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseKeepdirectories() { | |
| parser.parse(Paths.get(KEEPDIRECTORIES)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseDontshrink() { | |
| parser.parse(Paths.get(DONT_SHRINK)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertFalse(config.isShrinking()); | |
| } | |
| @Test | |
| public void parseDontSkipNonPublicLibraryClasses() { | |
| parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseDontskipnonpubliclibraryclassmembers() { | |
| parser.parse(Paths.get(DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseIdentifiernamestring() { | |
| Path source = Paths.get(IDENTIFIER_NAME_STRING); | |
| parser.parse(source); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseOverloadAggressively() { | |
| parser.parse(Paths.get(OVERLOAD_AGGRESIVELY)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseDontOptimize() { | |
| parser.parse(Paths.get(DONT_OPTIMIZE)); | |
| ProguardConfiguration config = builder.build(); | |
| verifyParserEndsCleanly(); | |
| assertFalse(config.isOptimizing()); | |
| } | |
| @Test | |
| public void parseDontOptimizeOverridesPasses() { | |
| Path path = Paths.get(DONT_OPTIMIZE_OVERRIDES_PASSES); | |
| parser.parse(path); | |
| checkDiagnostics(handler.infos, path, 7, 1, "Ignoring", "-optimizationpasses"); | |
| ProguardConfiguration config = builder.build(); | |
| assertFalse(config.isOptimizing()); | |
| } | |
| @Test | |
| public void parseOptimizationPasses() { | |
| Path path = Paths.get(OPTIMIZATION_PASSES); | |
| parser.parse(path); | |
| checkDiagnostics(handler.infos, path, 5, 1, "Ignoring", "-optimizationpasses"); | |
| ProguardConfiguration config = builder.build(); | |
| assertTrue(config.isOptimizing()); | |
| } | |
| @Test | |
| public void parseOptimizationPassesError() { | |
| Path path = Paths.get(OPTIMIZATION_PASSES_WITHOUT_N); | |
| try { | |
| 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(option)); | |
| fail("Expect to fail due to unsupported option."); | |
| } catch (RuntimeException e) { | |
| checkDiagnostics(handler.errors, null, 1, 1, "Unsupported option", option); | |
| } | |
| } | |
| @Test | |
| public void parseAndskipSingleArgument() { | |
| parser.parse(Paths.get(PARSE_AND_SKIP_SINGLE_ARGUMENT)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parse_printconfiguration_noArguments() { | |
| parser.parse(createConfigurationForTesting("-printconfiguration")); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertTrue(config.isPrintConfiguration()); | |
| assertNull(config.getPrintConfigurationFile()); | |
| } | |
| @Test | |
| public void parse_printconfiguration_argument() { | |
| parser.parse(createConfigurationForTesting("-printconfiguration file_name")); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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")); | |
| 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() { | |
| parser.parse(Paths.get(PRINT_USAGE)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertTrue(config.isPrintUsage()); | |
| assertNull(config.getPrintUsageFile()); | |
| } | |
| @Test | |
| public void parsePrintUsageToFile() { | |
| parser.parse(Paths.get(PRINT_USAGE_TO_FILE)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertTrue(config.isPrintUsage()); | |
| assertNotNull(config.getPrintUsageFile()); | |
| } | |
| @Test | |
| public void parseTarget() { | |
| parser.parse(Paths.get(TARGET)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseInvalidKeepOption() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-keepx public class * { ", | |
| " native <methods>; ", | |
| "} " | |
| ); | |
| try { | |
| 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 { | |
| 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 { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 1, 1, | |
| "Unknown option", "-keepclassx"); | |
| } | |
| } | |
| @Test | |
| public void parseCustomFlags() throws Exception { | |
| // 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")); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.buildRaw(); | |
| assertEquals("PG", config.getRenameSourceFileAttribute()); | |
| assertTrue(config.getKeepAttributes().sourceFile); | |
| } | |
| } | |
| @Test | |
| public void testRenameSourceFileAttribute_empty() throws Exception { | |
| for (String lineSeparator : lineSeparators) { | |
| reset(); | |
| Path proguardConfig = writeTextToTempFile(lineSeparator, | |
| ImmutableList.of( | |
| "-renamesourcefileattribute", | |
| "-keepattributes SourceFile")); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.buildRaw(); | |
| assertEquals("", config.getRenameSourceFileAttribute()); | |
| assertTrue(config.getKeepAttributes().sourceFile); | |
| } | |
| } | |
| @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")); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.buildRaw(); | |
| assertEquals("PG", config.getRenameSourceFileAttribute()); | |
| assertTrue(config.getKeepAttributes().sourceFile); | |
| } | |
| } | |
| } | |
| @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")); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.buildRaw(); | |
| assertEquals("", config.getRenameSourceFileAttribute()); | |
| assertTrue(config.getKeepAttributes().sourceFile); | |
| } | |
| } | |
| } | |
| private void testKeepattributes(List<String> expected, String config) { | |
| parser.parse(createConfigurationForTesting(config)); | |
| verifyParserEndsCleanly(); | |
| assertEquals( | |
| ProguardKeepAttributes.fromPatterns(expected), builder.buildRaw().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(String config) { | |
| parser.parse(createConfigurationForTesting(config)); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseKeeppackagenames() { | |
| testKeeppackagenames("-keeppackagenames xxx,yyy"); | |
| testKeeppackagenames("-keeppackagenames xxx, yyy"); | |
| testKeeppackagenames("-keeppackagenames xxx ,yyy"); | |
| testKeeppackagenames("-keeppackagenames xxx , yyy"); | |
| testKeeppackagenames("-keeppackagenames xxx , yyy "); | |
| testKeeppackagenames("-keeppackagenames xxx , yyy \n"); | |
| testKeeppackagenames("-keeppackagenames \"xxx\",\"yyy\""); | |
| testKeeppackagenames( | |
| "-keeppackagenames com.**, org.*"); | |
| testKeeppackagenames( | |
| "-keeppackagenames c?m.**, ?r?.*"); | |
| testKeeppackagenames( | |
| "-keeppackagenames !c?m.**, !?r?.*"); | |
| } | |
| @Test | |
| public void parseInvalidKeepattributes_brokenList() { | |
| try { | |
| parser.parse(createConfigurationForTesting("-keepattributes xxx,")); | |
| fail(); | |
| } catch (RuntimeException e) { | |
| assertTrue( | |
| handler.errors.get(0).getDiagnosticMessage().contains("Expected list element at ")); | |
| } | |
| } | |
| @Test | |
| public void parseUseUniqueClassMemberNames() throws IOException { | |
| Path proguardConfig = writeTextToTempFile("-useuniqueclassmembernames"); | |
| parser.parse(proguardConfig); | |
| checkDiagnostics( | |
| handler.warnings, proguardConfig, 1, 1, "Ignoring", "-useuniqueclassmembernames"); | |
| } | |
| @Test | |
| public void parseKeepParameterNamesWithoutMinification() { | |
| parser.parse( | |
| createConfigurationForTesting(StringUtils.lines("-keepparameternames", "-dontobfuscate"))); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertTrue(config.isKeepParameterNames()); | |
| parser.parse(createConfigurationForTesting("-keepparameternames")); | |
| verifyParserEndsCleanly(); | |
| parser.parse(createConfigurationForTesting("-dontobfuscate")); | |
| verifyParserEndsCleanly(); | |
| config = builder.build(); | |
| assertTrue(config.isKeepParameterNames()); | |
| } | |
| @Test | |
| public void parseShortLine() { | |
| try { | |
| parser.parse(createConfigurationForTesting("-")); | |
| fail(); | |
| } catch (AbortException e) { | |
| assertEquals(1, handler.errors.size()); | |
| assertTrue(handler.errors.get(0).getDiagnosticMessage().contains("-")); | |
| } | |
| } | |
| @Test | |
| public void parseNoLocals() { | |
| try { | |
| parser.parse(createConfigurationForTesting("--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( | |
| StringUtils.lines( | |
| "-adaptresourcefilenames", "-adaptresourcefilecontents", "-keepdirectories")); | |
| checkFileFilterMatchAnything(config.getAdaptResourceFilenames()); | |
| checkFileFilterMatchAnything(config.getAdaptResourceFileContents()); | |
| checkFileFilterMatchAnything(config.getKeepDirectories()); | |
| } | |
| @Test | |
| public void parse_adaptresourcexxx_keepdirectories_noArguments2() { | |
| ProguardConfiguration config = | |
| parseAndVerifyParserEndsCleanly( | |
| StringUtils.lines( | |
| "-keepdirectories", "-adaptresourcefilenames", "-adaptresourcefilecontents")); | |
| checkFileFilterMatchAnything(config.getAdaptResourceFilenames()); | |
| checkFileFilterMatchAnything(config.getAdaptResourceFileContents()); | |
| checkFileFilterMatchAnything(config.getKeepDirectories()); | |
| } | |
| @Test | |
| public void parse_adaptresourcexxx_keepdirectories_noArguments3() { | |
| ProguardConfiguration config = | |
| parseAndVerifyParserEndsCleanly( | |
| StringUtils.lines( | |
| "-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( | |
| StringUtils.lines( | |
| "-adaptresourcefilenames " + FILE_FILTER_SINGLE, | |
| "-adaptresourcefilecontents " + FILE_FILTER_SINGLE, | |
| "-keepdirectories " + FILE_FILTER_SINGLE)); | |
| checkFileFilterSingle(config.getAdaptResourceFilenames()); | |
| checkFileFilterSingle(config.getAdaptResourceFileContents()); | |
| checkFileFilterSingle(config.getKeepDirectories()); | |
| } | |
| 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() { | |
| String FILE_FILTER_MULTIPLE = | |
| "xxx/*, !**.gif ,images/** , com/myapp/**/*.xml,com/mylib/*/*.xml"; | |
| ProguardConfiguration config = | |
| parseAndVerifyParserEndsCleanly( | |
| StringUtils.lines( | |
| "-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(option + " ,")); | |
| fail("Expect to fail due to the lack of path filter."); | |
| } catch (RuntimeException 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(option + " xxx,,yyy")); | |
| fail("Expect to fail due to the lack of path filter."); | |
| } catch (RuntimeException 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(option + " xxx,")); | |
| fail("Expect to fail due to the lack of path filter."); | |
| } catch (RuntimeException e) { | |
| checkDiagnostics(handler.errors, null, 1, option.length() + 6, "Path filter expected"); | |
| } | |
| } | |
| } | |
| @Test | |
| public void parse_testInlineOptions() { | |
| List<String> options = ImmutableList.of("-neverinline"); | |
| for (String option : options) { | |
| try { | |
| reset(); | |
| parser.parse(createConfigurationForTesting(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" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| // 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. | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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); | |
| } | |
| @Test | |
| public void parse_if_fieldType() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **.R*", | |
| "-keep class **.D<2> {", | |
| " <1>.F<2> fld;", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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()); | |
| } | |
| @Test | |
| public void parse_if_fieldName() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **.R*", | |
| "-keep class **.D<2> {", | |
| " java.lang.String fld<2>;", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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()); | |
| } | |
| @Test | |
| public void parse_if_returnType() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **.R*", | |
| "-keep class **.D<2> {", | |
| " <1>.M<2> mtd(...);", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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()); | |
| } | |
| @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 (RuntimeException e) { | |
| checkDiagnostics( | |
| handler.errors, proguardConfig, 5, 3, "Unexpected character", "method name"); | |
| } | |
| } | |
| @Test | |
| public void parse_if_methodName_void() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **.R* {", | |
| " void *(...);", | |
| "}", | |
| "-keep class **.D<2> {", | |
| " void <3>_delegate(...);", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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()); | |
| } | |
| @Test | |
| public void parse_if_methodName_class() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **.R* {", | |
| " ** *(...);", | |
| "}", | |
| "-keep class **.D<2> {", | |
| " <3> <4>(...);", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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()); | |
| } | |
| @Test | |
| public void parse_if_nthWildcard_notNumber_literalN() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **$R**", | |
| "-keep class **D<n>" | |
| ); | |
| try { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 2, 13, | |
| "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 { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 2, 13, | |
| "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<*>;", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parse_if_nestedAngularBrackets_inMemberName() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **$R**", | |
| "-keep class **D<2> {", | |
| " int id<<*>>;", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parse_if_closeBracketBeforeOpenBracket_inMemberName() throws Exception { | |
| Path proguardConfig = | |
| writeTextToTempFile("-if class **$R**", "-keep class **D<2> {", " int id>1<;", "}"); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| } | |
| @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 { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 4, 2, | |
| "Wildcard", "<4>", "invalid"); | |
| } | |
| } | |
| @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 { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 2, 13, | |
| "Wildcard", "<0>", "invalid"); | |
| } | |
| } | |
| @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 { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 3, 1, | |
| "Wildcard", "<4>", "invalid"); | |
| } | |
| } | |
| @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 { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 3, 1, | |
| "Wildcard", "<2>", "invalid"); | |
| } | |
| } | |
| @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 { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 6, 2, | |
| "Wildcard", "<3>", "invalid"); | |
| } | |
| } | |
| @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 { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 5, 1, | |
| "Wildcard", "<3>", "invalid"); | |
| } | |
| } | |
| @Test | |
| public void parse_if_if() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **$$ModuleAdapter", | |
| "-if class **$$InjectAdapter" | |
| ); | |
| try { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 1, 1, | |
| "Expecting", "'-keep'", "after", "'-if'"); | |
| } | |
| } | |
| @Test | |
| public void parse_if_end() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-if class **$$ModuleAdapter" | |
| ); | |
| try { | |
| parser.parse(proguardConfig); | |
| fail(); | |
| } catch (AbortException e) { | |
| checkDiagnostics(handler.errors, proguardConfig, 1, 1, | |
| "Expecting", "'-keep'", "after", "'-if'"); | |
| } | |
| } | |
| @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();", | |
| "}" | |
| ); | |
| 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);", | |
| "}" | |
| ); | |
| 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);", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| checkDiagnostics(handler.warnings, proguardConfig, 1, 1, | |
| "Ignoring", "-assumenoexternalreturnvalues"); | |
| } | |
| @Test | |
| public void parse_dump_withoutFile() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-dump" | |
| ); | |
| 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" | |
| ); | |
| parser.parse(proguardConfig); | |
| checkDiagnostics(handler.warnings, proguardConfig, 1, 1, | |
| "Ignoring", "-dump"); | |
| } | |
| @Test | |
| public void parse_android() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-android" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parse_mergeinterfaceaggressively() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-mergeinterfacesaggressively" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parse_addconfigurationdebugging() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-addconfigurationdebugging" | |
| ); | |
| parser.parse(proguardConfig); | |
| assertEquals(0, handler.infos.size()); | |
| assertEquals(1, handler.warnings.size()); | |
| assertEquals(0, handler.errors.size()); | |
| } | |
| @Test | |
| public void parse_regress74508478() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-keep class A {", | |
| " A <fields>;", | |
| "}" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parse_regress79925760() throws Exception { | |
| Path proguardConfig = writeTextToTempFile( | |
| "-keep public @ interface test.MyAnnotation" | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(1, config.getRules().size()); | |
| ProguardKeepRule rule = (ProguardKeepRule) config.getRules().get(0); | |
| assertEquals(ProguardClassType.ANNOTATION_INTERFACE, rule.getClassType()); | |
| } | |
| @Test | |
| public void parse_regress110021323() throws Exception { | |
| Path proguardConfig = | |
| writeTextToTempFile( | |
| "-keepclassmembernames class A {", " <public methods>;", " <public fields>;", "}"); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parse_regress433451526() throws Exception { | |
| Path proguardConfig = | |
| writeTextToTempFile( | |
| "-if @kotlinx.serialization.Serializable class **", | |
| "-keepclassmembers class <1> {", | |
| " static <1>$* Companion;", | |
| "}"); | |
| parser.parse(proguardConfig); | |
| assertEquals(0, handler.warnings.size()); | |
| } | |
| private void checkRulesSourceSnippet(String sourceRule) { | |
| checkRulesSourceSnippet(sourceRule, ImmutableList.of(sourceRule), false); | |
| } | |
| private void checkRulesSourceSnippet(String sourceRules, List<String> expected, boolean trim) { | |
| reset(); | |
| parser.parse(createConfigurationForTesting(sourceRules)); | |
| verifyParserEndsCleanly(); | |
| List<ProguardConfigurationRule> rules = builder.build().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 = "-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(rule1); | |
| checkRulesSourceSnippet(rule2); | |
| checkRulesSourceSnippet(rule3); | |
| checkRulesSourceSnippet( | |
| StringUtils.lines(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(rule1, ImmutableList.of(rule1), true); | |
| checkRulesSourceSnippet( | |
| StringUtils.lines("#Test comment ", "", rule1), ImmutableList.of(rule1), true); | |
| checkRulesSourceSnippet( | |
| StringUtils.lines("#Test comment ", "", rule1, "", "#Test comment ", ""), | |
| ImmutableList.of(rule1), | |
| true); | |
| checkRulesSourceSnippet(rule2, ImmutableList.of(rule2), true); | |
| checkRulesSourceSnippet(StringUtils.lines(rule1, rule2), ImmutableList.of(rule1), true); | |
| checkRulesSourceSnippet( | |
| StringUtils.lines( | |
| "#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() | |
| ); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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(flag + " " + value)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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(flag + " " + value)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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("-printusage <>")); | |
| fail("Expect to fail due to the lack of file name."); | |
| } catch (RuntimeException 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("-printusage <" + property + ">")); | |
| fail("Expect to fail due to the lack of file name."); | |
| } catch (RuntimeException 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(flag + " " + value)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| checker.accept(config.getPackageObfuscationMode(), config.getPackagePrefix()); | |
| } | |
| private void testFlagWithQuotedValueFailure( | |
| String flag, String value, Supplier<Void> checker) { | |
| try { | |
| reset(); | |
| parser.parse(createConfigurationForTesting(flag + " " + value)); | |
| fail("Expect to fail due to un-closed quote."); | |
| } catch (RuntimeException 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(String config) { | |
| parser.parse(createConfigurationForTesting(config)); | |
| verifyParserEndsCleanly(); | |
| return builder.build(); | |
| } | |
| private ProguardConfiguration parseAndVerifyParserEndsCleanly(Path config) { | |
| parser.parse(config); | |
| verifyParserEndsCleanly(); | |
| return builder.build(); | |
| } | |
| private void verifyParserEndsCleanly() { | |
| assertEquals(0, handler.infos.size()); | |
| assertEquals(0, handler.warnings.size()); | |
| assertEquals(0, handler.errors.size()); | |
| } | |
| @Test | |
| public void b124181032() { | |
| // Test spaces and quotes in class name list. | |
| parser.parse( | |
| createConfigurationForTesting( | |
| StringUtils.lines( | |
| "-keepclassmembers class \"a.b.c.**\" ," | |
| + " !**d , '!**e' , \"!**f\" , g , 'h' , \"i\" { ", | |
| "<fields>;", | |
| "<init>();", | |
| "}"))); | |
| List<ProguardConfigurationRule> rules = builder.build().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"; | |
| parser.parse(createConfigurationForTesting(directive + " -keep class *")); | |
| ProguardConfiguration configuration = builder.build(); | |
| 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"; | |
| parser.parse(createConfigurationForTesting(directive + " @" + includeFile.toAbsolutePath())); | |
| ProguardConfiguration configuration = builder.build(); | |
| 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 { | |
| parser.parse(proguardConfigurationFile); | |
| fail("Expected to fail since the type name cannot be negated."); | |
| } catch (RuntimeException 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 (RuntimeException 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; | |
| } | |
| } | |
| } | |
| @Test | |
| public void parseIncludeCode() throws Exception { | |
| Path proguardConfig = writeTextToTempFile("-keep,includecode class A { method(); }"); | |
| parser.parse(proguardConfig); | |
| assertEquals(1, builder.build().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. | |
| 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, builder.build().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, builder.build().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() { | |
| String configuration = StringUtils.lines("-if class *.*.*", "-keep class <1>.<2>$<3>"); | |
| parser.parse(createConfigurationForTesting(configuration)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| 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 *"); | |
| parser.parse(proguardConfig); | |
| verifyParserEndsCleanly(); | |
| } | |
| @Test | |
| public void parseClassAnnotationsAndFlags() { | |
| List<String> configurationContents = | |
| ImmutableList.of( | |
| // 1 annotation without flags. | |
| "-keep @Foo class *", | |
| // 1 annotation with public flag. | |
| "-keep public @Foo class *", | |
| "-keep @Foo public class *", | |
| // 1 annotation with public final flags. | |
| "-keep public final @Foo class *", | |
| "-keep public @Foo final class *", | |
| "-keep @Foo public final class *", | |
| // 2 annotations without flags. | |
| "-keep @Foo @Bar class *", | |
| // 2 annotations with public flag. | |
| "-keep public @Foo @Bar class *", | |
| "-keep @Foo public @Bar class *", | |
| "-keep @Foo @Bar public class *", | |
| // 2 annotations with public final flags. | |
| "-keep public final @Foo @Bar class *", | |
| "-keep public @Foo final @Bar class *", | |
| "-keep public @Foo @Bar final class *", | |
| "-keep @Foo public final @Bar class *", | |
| "-keep @Foo public @Bar final class *", | |
| "-keep @Foo @Bar public final class *"); | |
| for (String configurationContent : configurationContents) { | |
| reset(); | |
| parser.parse(createConfigurationForTesting(configurationContent)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration configuration = builder.build(); | |
| assertEquals(1, configuration.getRules().size()); | |
| ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule(); | |
| assertEquals(configurationContent.contains("final"), rule.getClassAccessFlags().isFinal()); | |
| assertEquals(configurationContent.contains("public"), rule.getClassAccessFlags().isPublic()); | |
| assertEquals( | |
| 1 + intValue(configurationContent.contains("@Bar")), rule.getClassAnnotations().size()); | |
| ProguardTypeMatcher.MatchSpecificType fooAnnotation = | |
| rule.getClassAnnotations().get(0).asSpecificTypeMatcher(); | |
| assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString()); | |
| if (configurationContent.contains("@Bar")) { | |
| ProguardTypeMatcher.MatchSpecificType barAnnotation = | |
| rule.getClassAnnotations().get(1).asSpecificTypeMatcher(); | |
| assertEquals("Bar", barAnnotation.getSpecificType().toSourceString()); | |
| } | |
| } | |
| } | |
| @Test | |
| public void parseFieldAnnotationsAndFlags() { | |
| Map<String, Optional<Class<? extends Exception>>> configurationContents = | |
| ImmutableMap.<String, Optional<Class<? extends Exception>>>builder() | |
| // 1 annotation without flags. | |
| .put("-keep class * { @Foo Type FIELD; }", Optional.empty()) | |
| // 1 annotation with public flag. | |
| .put("-keep class * { public @Foo Type FIELD; }", Optional.of(RuntimeException.class)) | |
| .put("-keep class * { @Foo public Type FIELD; }", Optional.empty()) | |
| // 1 annotation with public final flags. | |
| .put( | |
| "-keep class * { public final @Foo Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { public @Foo final Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put("-keep class * { @Foo public final Type FIELD; }", Optional.empty()) | |
| //// 2 annotations without flags. | |
| .put("-keep class * { @Foo @Bar Type FIELD; }", Optional.empty()) | |
| //// 2 annotations with public flag. | |
| .put( | |
| "-keep class * { public @Foo @Bar Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { @Foo public @Bar Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put("-keep class * { @Foo @Bar public Type FIELD; }", Optional.empty()) | |
| //// 2 annotations with public final flags. | |
| .put( | |
| "-keep class * { public final @Foo @Bar Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { public @Foo final @Bar Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { public @Foo @Bar final Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { @Foo public final @Bar Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { @Foo public @Bar final Type FIELD; }", | |
| Optional.of(RuntimeException.class)) | |
| .put("-keep class * { @Foo @Bar public final Type FIELD; }", Optional.empty()) | |
| .build(); | |
| configurationContents.forEach( | |
| (configurationContent, expectedExceptionClass) -> { | |
| reset(); | |
| try { | |
| parser.parse(createConfigurationForTesting(configurationContent)); | |
| assertFalse(expectedExceptionClass.isPresent()); | |
| } catch (Throwable e) { | |
| assertTrue(expectedExceptionClass.isPresent()); | |
| assertEquals(expectedExceptionClass.get(), e.getClass()); | |
| reset(); | |
| return; | |
| } | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration configuration = builder.build(); | |
| assertEquals(1, configuration.getRules().size()); | |
| ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule(); | |
| assertEquals(1, rule.getMemberRules().size()); | |
| ProguardMemberRule memberRule = ListUtils.first(rule.getMemberRules()); | |
| assertEquals( | |
| configurationContent.contains("final"), memberRule.getAccessFlags().isFinal()); | |
| assertEquals( | |
| configurationContent.contains("public"), memberRule.getAccessFlags().isPublic()); | |
| assertEquals( | |
| 1 + intValue(configurationContent.contains("@Bar")), | |
| memberRule.getAnnotations().size()); | |
| ProguardTypeMatcher.MatchSpecificType fooAnnotation = | |
| memberRule.getAnnotations().get(0).asSpecificTypeMatcher(); | |
| assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString()); | |
| if (configurationContent.contains("@Bar")) { | |
| ProguardTypeMatcher.MatchSpecificType barAnnotation = | |
| memberRule.getAnnotations().get(1).asSpecificTypeMatcher(); | |
| assertEquals("Bar", barAnnotation.getSpecificType().toSourceString()); | |
| } | |
| }); | |
| } | |
| @Test | |
| public void parseMethodAnnotationsAndFlags() { | |
| Map<String, Optional<Class<? extends Exception>>> configurationContents = | |
| ImmutableMap.<String, Optional<Class<? extends Exception>>>builder() | |
| // 1 annotation without flags. | |
| .put("-keep class * { @Foo Type method(); }", Optional.empty()) | |
| // 1 annotation with public flag. | |
| .put( | |
| "-keep class * { public @Foo Type method(); }", Optional.of(RuntimeException.class)) | |
| .put("-keep class * { @Foo public Type method(); }", Optional.empty()) | |
| // 1 annotation with public final flags. | |
| .put( | |
| "-keep class * { public final @Foo Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { public @Foo final Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put("-keep class * { @Foo public final Type method(); }", Optional.empty()) | |
| //// 2 annotations without flags. | |
| .put("-keep class * { @Foo @Bar Type method(); }", Optional.empty()) | |
| //// 2 annotations with public flag. | |
| .put( | |
| "-keep class * { public @Foo @Bar Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { @Foo public @Bar Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put("-keep class * { @Foo @Bar public Type method(); }", Optional.empty()) | |
| //// 2 annotations with public final flags. | |
| .put( | |
| "-keep class * { public final @Foo @Bar Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { public @Foo final @Bar Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { public @Foo @Bar final Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { @Foo public final @Bar Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put( | |
| "-keep class * { @Foo public @Bar final Type method(); }", | |
| Optional.of(RuntimeException.class)) | |
| .put("-keep class * { @Foo @Bar public final Type method(); }", Optional.empty()) | |
| .build(); | |
| configurationContents.forEach( | |
| (configurationContent, expectedExceptionClass) -> { | |
| reset(); | |
| try { | |
| parser.parse(createConfigurationForTesting(configurationContent)); | |
| assertFalse(expectedExceptionClass.isPresent()); | |
| } catch (Throwable e) { | |
| assertTrue(expectedExceptionClass.isPresent()); | |
| assertEquals(expectedExceptionClass.get(), e.getClass()); | |
| reset(); | |
| return; | |
| } | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration configuration = builder.build(); | |
| assertEquals(1, configuration.getRules().size()); | |
| ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule(); | |
| assertEquals(1, rule.getMemberRules().size()); | |
| ProguardMemberRule memberRule = ListUtils.first(rule.getMemberRules()); | |
| assertEquals( | |
| configurationContent.contains("final"), memberRule.getAccessFlags().isFinal()); | |
| assertEquals( | |
| configurationContent.contains("public"), memberRule.getAccessFlags().isPublic()); | |
| assertEquals( | |
| 1 + intValue(configurationContent.contains("@Bar")), | |
| memberRule.getAnnotations().size()); | |
| ProguardTypeMatcher.MatchSpecificType fooAnnotation = | |
| memberRule.getAnnotations().get(0).asSpecificTypeMatcher(); | |
| assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString()); | |
| if (configurationContent.contains("@Bar")) { | |
| ProguardTypeMatcher.MatchSpecificType barAnnotation = | |
| memberRule.getAnnotations().get(1).asSpecificTypeMatcher(); | |
| assertEquals("Bar", barAnnotation.getSpecificType().toSourceString()); | |
| } | |
| }); | |
| } | |
| @Test | |
| public void parseKeepXNamesIsAllowShrinking() { | |
| for (String rule : ImmutableList.of("-keep", "-keepclassmembers", "-keepclasseswithmembers")) { | |
| for (String modifier : | |
| ImmutableList.of("", "names", ",allowshrinking", "names,allowshrinking")) { | |
| reset(); | |
| String ruleWithModifier = | |
| (rule.endsWith("s") && (!modifier.startsWith(",") && !modifier.isEmpty()) | |
| ? rule.substring(0, rule.length() - 1) | |
| : rule) | |
| + modifier; | |
| parser.parse(createConfigurationForTesting(ruleWithModifier + " class A")); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration configuration = builder.build(); | |
| assertEquals(1, configuration.getRules().size()); | |
| assertEquals( | |
| !modifier.isEmpty(), | |
| ListUtils.first(configuration.getRules()) | |
| .asProguardKeepRule() | |
| .getModifiers() | |
| .allowsShrinking); | |
| } | |
| } | |
| } | |
| @Test | |
| public void parseMaximumRemovedAndroidLogLevelWithoutClassSpecification() { | |
| String configuration = StringUtils.lines("-maximumremovedandroidloglevel 2"); | |
| parser.parse(createConfigurationForTesting(configuration)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals(MaximumRemovedAndroidLogLevelRule.VERBOSE, config.getMaxRemovedAndroidLogLevel()); | |
| assertEquals(0, config.getRules().size()); | |
| } | |
| @Test | |
| public void parseMaximumRemovedAndroidLogLevelWithClassSpecification() { | |
| for (String input : | |
| new String[] { | |
| "-maximumremovedandroidloglevel 2 class * { <methods>; }", | |
| "-maximumremovedandroidloglevel 2 @Foo class * { <methods>; }" | |
| }) { | |
| reset(); | |
| String configuration = StringUtils.lines(input); | |
| parser.parse(createConfigurationForTesting(configuration)); | |
| verifyParserEndsCleanly(); | |
| ProguardConfiguration config = builder.build(); | |
| assertEquals( | |
| MaximumRemovedAndroidLogLevelRule.NOT_SET, config.getMaxRemovedAndroidLogLevel()); | |
| assertEquals(1, config.getRules().size()); | |
| assertTrue(config.getRules().get(0).isMaximumRemovedAndroidLogLevelRule()); | |
| MaximumRemovedAndroidLogLevelRule rule = | |
| config.getRules().get(0).asMaximumRemovedAndroidLogLevelRule(); | |
| assertEquals(MaximumRemovedAndroidLogLevelRule.VERBOSE, rule.getMaxRemovedAndroidLogLevel()); | |
| } | |
| } | |
| @Test | |
| public void testParsedConfigurationWithInclude() throws Exception { | |
| Path config = temp.newFile("config.txt").toPath().toAbsolutePath(); | |
| Path include1 = temp.newFile("include1.txt").toPath().toAbsolutePath(); | |
| Path include11 = temp.newFile("include1_1.txt").toPath().toAbsolutePath(); | |
| Path include2 = temp.newFile("include2.txt").toPath().toAbsolutePath(); | |
| FileUtils.writeTextFile( | |
| config, | |
| StringUtils.joinLines( | |
| "# Before Include 1", | |
| "-include " + include1, | |
| "# After Include 1", | |
| "-include " + include2, | |
| "# After Include 2")); | |
| FileUtils.writeTextFile( | |
| include1, StringUtils.joinLines("# Include 1", "-include " + include11)); | |
| FileUtils.writeTextFile(include11, "# Include 1.1"); | |
| FileUtils.writeTextFile(include2, "# Include 2"); | |
| parser.parse(config); | |
| verifyParserEndsCleanly(); | |
| String parsedConfiguration = builder.build().getParsedConfiguration(); | |
| String separator = ToolHelper.isWindows() ? "\\" : "/"; | |
| assertEquals( | |
| StringUtils.lines( | |
| "# The proguard configuration file for the following section is config.txt", | |
| "# Before Include 1", | |
| "", // -include include1.txt | |
| "# After Include 1", | |
| "", // -include include2.txt | |
| "# After Include 2", | |
| "# End of content from config.txt", | |
| "# The proguard configuration file for the following section is include1.txt", | |
| "# Include 1", | |
| "", // -include include1_1.txt. | |
| "# End of content from include1.txt", | |
| "# The proguard configuration file for the following section is include1_1.txt", | |
| "# Include 1.1", | |
| "# End of content from include1_1.txt", | |
| "# The proguard configuration file for the following section is include2.txt", | |
| "# Include 2", | |
| "# End of content from include2.txt"), | |
| StringUtils.replaceAll(parsedConfiguration, temp.getRoot().toString() + separator, "")); | |
| } | |
| } |