Home | History | Annotate | Download | only in shaking
      1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
      2 // for details. All rights reserved. Use of this source code is governed by a
      3 // BSD-style license that can be found in the LICENSE file.
      4 package com.android.tools.r8.shaking;
      5 
      6 import com.android.tools.r8.dex.Constants;
      7 import com.android.tools.r8.graph.DexAccessFlags;
      8 import com.android.tools.r8.graph.DexField;
      9 import com.android.tools.r8.graph.DexItemFactory;
     10 import com.android.tools.r8.graph.DexString;
     11 import com.android.tools.r8.graph.DexType;
     12 import com.android.tools.r8.logging.Log;
     13 import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
     14 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
     15 import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
     16 import com.android.tools.r8.utils.DescriptorUtils;
     17 import com.android.tools.r8.utils.LongInterval;
     18 import com.google.common.collect.ImmutableList;
     19 import com.google.common.collect.Iterables;
     20 import java.io.File;
     21 import java.io.FileNotFoundException;
     22 import java.io.IOException;
     23 import java.nio.CharBuffer;
     24 import java.nio.charset.StandardCharsets;
     25 import java.nio.file.Files;
     26 import java.nio.file.NoSuchFileException;
     27 import java.nio.file.Path;
     28 import java.nio.file.Paths;
     29 import java.util.ArrayList;
     30 import java.util.Collections;
     31 import java.util.List;
     32 
     33 public class ProguardConfigurationParser {
     34 
     35   private final Builder configurationBuilder;
     36 
     37   private final DexItemFactory dexItemFactory;
     38 
     39   private static final List<String> ignoredSingleArgOptions = ImmutableList
     40       .of("protomapping",
     41           "optimizationpasses",
     42           "target");
     43   private static final List<String> ignoredOptionalSingleArgOptions = ImmutableList
     44       .of("keepdirectories", "runtype", "laststageoutput");
     45   private static final List<String> ignoredFlagOptions = ImmutableList
     46       .of("forceprocessing", "dontusemixedcaseclassnames",
     47           "dontpreverify", "experimentalshrinkunusedprotofields",
     48           "filterlibraryjarswithorginalprogramjars",
     49           "dontskipnonpubliclibraryclasses",
     50           "dontskipnonpubliclibraryclassmembers",
     51           "overloadaggressively",
     52           "invokebasemethod");
     53   private static final List<String> ignoredClassDescriptorOptions = ImmutableList
     54       .of("isclassnamestring",
     55           "alwaysinline", "identifiernamestring", "whyarenotsimple");
     56 
     57   private static final List<String> warnedSingleArgOptions = ImmutableList
     58       .of("renamesourcefileattribute",
     59           "dontnote",
     60           "printconfiguration",
     61           // TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
     62           // should be reported as errors, not just as warnings!
     63           "outjars",
     64           "adaptresourcefilecontents");
     65   private static final List<String> warnedFlagOptions = ImmutableList
     66       .of("dontoptimize");
     67 
     68   // Those options are unsupported and are treated as compilation errors.
     69   // Just ignoring them would produce outputs incompatible with user expectations.
     70   private static final List<String> unsupportedFlagOptions = ImmutableList
     71       .of("skipnonpubliclibraryclasses");
     72 
     73   public ProguardConfigurationParser(DexItemFactory dexItemFactory) {
     74     this.dexItemFactory = dexItemFactory;
     75     configurationBuilder = ProguardConfiguration.builder(dexItemFactory);
     76   }
     77 
     78   public ProguardConfiguration getConfig() {
     79     return configurationBuilder.build();
     80   }
     81 
     82   public void parse(Path path) throws ProguardRuleParserException, IOException {
     83     parse(Collections.singletonList(path));
     84   }
     85 
     86   public void parse(List<Path> pathList) throws ProguardRuleParserException, IOException {
     87     for (Path path : pathList) {
     88       new ProguardFileParser(path).parse();
     89     }
     90   }
     91 
     92   private class ProguardFileParser {
     93 
     94     private final Path path;
     95     private final String contents;
     96     private int position = 0;
     97     private Path baseDirectory;
     98 
     99     public ProguardFileParser(Path path) throws IOException {
    100       this.path = path;
    101       contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
    102       baseDirectory = path.getParent();
    103       if (baseDirectory == null) {
    104         // path parent can be null only if it's root dir or if its a one element path relative to
    105         // current directory.
    106         baseDirectory = Paths.get(".");
    107       }
    108     }
    109 
    110     public void parse() throws ProguardRuleParserException {
    111       do {
    112         skipWhitespace();
    113       } while (parseOption());
    114     }
    115 
    116     private boolean parseOption() throws ProguardRuleParserException {
    117       if (eof()) {
    118         return false;
    119       }
    120       if (acceptArobaseInclude()) {
    121         return true;
    122       }
    123       expectChar('-');
    124       String option;
    125       if (Iterables.any(ignoredSingleArgOptions, this::skipOptionWithSingleArg)
    126           || Iterables.any(ignoredOptionalSingleArgOptions, this::skipOptionWithOptionalSingleArg)
    127           || Iterables.any(ignoredFlagOptions, this::skipFlag)
    128           || Iterables.any(ignoredClassDescriptorOptions, this::skipOptionWithClassSpec)
    129           || parseOptimizationOption()) {
    130         // Intentionally left empty.
    131       } else if (
    132              (option = Iterables.find(warnedSingleArgOptions,
    133                  this::skipOptionWithSingleArg, null)) != null
    134           || (option = Iterables.find(warnedFlagOptions, this::skipFlag, null)) != null) {
    135         System.out.println("WARNING: Ignoring option: -" + option);
    136       } else if ((option = Iterables.find(unsupportedFlagOptions, this::skipFlag, null)) != null) {
    137         throw parseError("Unsupported option: -" + option);
    138       } else if (acceptString("keepattributes")) {
    139         parseKeepAttributes();
    140       } else if (acceptString("keeppackagenames")) {
    141         ProguardKeepRule rule = parseKeepPackageNamesRule();
    142         configurationBuilder.addRule(rule);
    143       } else if (acceptString("checkdiscard")) {
    144         ProguardKeepRule rule = parseCheckDiscardRule();
    145         configurationBuilder.addRule(rule);
    146       } else if (acceptString("keep")) {
    147         ProguardKeepRule rule = parseKeepRule();
    148         configurationBuilder.addRule(rule);
    149       } else if (acceptString("whyareyoukeeping")) {
    150         ProguardKeepRule rule = parseWhyAreYouKeepingRule();
    151         configurationBuilder.addRule(rule);
    152       } else if (acceptString("dontobfuscate")) {
    153         configurationBuilder.setObfuscating(false);
    154       } else if (acceptString("dontshrink")) {
    155         configurationBuilder.setShrinking(false);
    156       } else if (acceptString("printusage")) {
    157         configurationBuilder.setPrintUsage(true);
    158         skipWhitespace();
    159         if (isOptionalArgumentGiven()) {
    160           configurationBuilder.setPrintUsageFile(parseFileName());
    161         }
    162         // TODO(b/36799826): once fully implemented, no longer necessary to warn.
    163         System.out.println("WARNING: Ignoring option: -printusage");
    164       } else if (acceptString("verbose")) {
    165         configurationBuilder.setVerbose(true);
    166       } else if (acceptString("ignorewarnings")) {
    167         configurationBuilder.setIgnoreWarnings(true);
    168       } else if (acceptString("dontwarn")) {
    169         do {
    170           ProguardTypeMatcher pattern = ProguardTypeMatcher.create(parseClassName(),
    171               ClassOrType.CLASS, dexItemFactory);
    172           configurationBuilder.addDontWarnPattern(pattern);
    173         } while (acceptChar(','));
    174       } else if (acceptString("repackageclasses")) {
    175         skipWhitespace();
    176         if (acceptChar('\'')) {
    177           configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
    178           expectChar('\'');
    179         } else {
    180           configurationBuilder.setPackagePrefix("");
    181         }
    182       } else if (acceptString("allowaccessmodification")) {
    183         configurationBuilder.setAllowAccessModification(true);
    184       } else if (acceptString("printmapping")) {
    185         configurationBuilder.setPrintMapping(true);
    186         skipWhitespace();
    187         if (isOptionalArgumentGiven()) {
    188           configurationBuilder.setPrintMappingOutput(parseFileName());
    189         }
    190       } else if (acceptString("assumenosideeffects")) {
    191         ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule();
    192         configurationBuilder.addRule(rule);
    193       } else if (acceptString("assumevalues")) {
    194         ProguardAssumeValuesRule rule = parseAssumeValuesRule();
    195         configurationBuilder.addRule(rule);
    196       } else if (acceptString("include")) {
    197         skipWhitespace();
    198         parseInclude();
    199       } else if (acceptString("basedirectory")) {
    200         skipWhitespace();
    201         baseDirectory = parseFileName();
    202       } else if (acceptString("injars")) {
    203         configurationBuilder.addInjars(parseClassPath());
    204       } else if (acceptString("libraryjars")) {
    205         configurationBuilder.addLibraryJars(parseClassPath());
    206       } else if (acceptString("printseeds")) {
    207         configurationBuilder.setPrintSeed(true);
    208         skipWhitespace();
    209         if (isOptionalArgumentGiven()) {
    210           configurationBuilder.setSeedFile(parseFileName());
    211         }
    212       } else if (acceptString("obfuscationdictionary")) {
    213         configurationBuilder.setObfuscationDictionary(parseFileName());
    214       } else if (acceptString("classobfuscationdictionary")) {
    215         configurationBuilder.setClassObfuscationDictionary(parseFileName());
    216       } else if (acceptString("packageobfuscationdictionary")) {
    217         configurationBuilder.setPackageObfuscationDictionary(parseFileName());
    218       } else {
    219         throw parseError("Unknown option");
    220       }
    221       return true;
    222     }
    223 
    224     private void parseInclude() throws ProguardRuleParserException {
    225       Path included = parseFileName();
    226       try {
    227         new ProguardFileParser(included).parse();
    228       } catch (FileNotFoundException | NoSuchFileException e) {
    229         throw parseError("Included file '" + included.toString() + "' not found", e);
    230       } catch (IOException e) {
    231         throw parseError("Failed to read included file '" + included.toString() + "'", e);
    232       }
    233     }
    234 
    235     private boolean acceptArobaseInclude() throws ProguardRuleParserException {
    236       if (remainingChars() < 2) {
    237         return false;
    238       }
    239       if (!acceptChar('@')) {
    240         return false;
    241       }
    242       parseInclude();
    243       return true;
    244     }
    245 
    246     private void parseKeepAttributes() throws ProguardRuleParserException {
    247       String attributesPattern = acceptPatternList();
    248       if (attributesPattern == null) {
    249         throw parseError("Expected attribute pattern list");
    250       }
    251       configurationBuilder.addAttributeRemovalPattern(attributesPattern);
    252     }
    253 
    254     private boolean skipFlag(String name) {
    255       if (acceptString(name)) {
    256         if (Log.ENABLED) {
    257           Log.debug(ProguardConfigurationParser.class, "Skipping '-%s` flag", name);
    258         }
    259         return true;
    260       }
    261       return false;
    262     }
    263 
    264     private boolean skipOptionWithSingleArg(String name) {
    265       if (acceptString(name)) {
    266         if (Log.ENABLED) {
    267           Log.debug(ProguardConfigurationParser.class, "Skipping '-%s` option", name);
    268         }
    269         skipSingleArgument();
    270         return true;
    271       }
    272       return false;
    273     }
    274 
    275     private boolean skipOptionWithOptionalSingleArg(String name) {
    276       if (acceptString(name)) {
    277         if (Log.ENABLED) {
    278           Log.debug(ProguardConfigurationParser.class, "Skipping '-%s` option", name);
    279         }
    280         skipWhitespace();
    281         if (isOptionalArgumentGiven()) {
    282           skipSingleArgument();
    283         }
    284         return true;
    285       }
    286       return false;
    287     }
    288 
    289     private boolean skipOptionWithClassSpec(String name) {
    290       if (acceptString(name)) {
    291         if (Log.ENABLED) {
    292           Log.debug(ProguardConfigurationParser.class, "Skipping '-%s` option", name);
    293         }
    294         try {
    295           ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
    296           parseClassFlagsAndAnnotations(keepRuleBuilder);
    297           keepRuleBuilder.setClassType(parseClassType());
    298           keepRuleBuilder.setClassNames(parseClassNames());
    299           parseInheritance(keepRuleBuilder);
    300           parseMemberRules(keepRuleBuilder, true);
    301           return true;
    302         } catch (ProguardRuleParserException e) {
    303           System.out.println(e);
    304           return false;
    305         }
    306       }
    307       return false;
    308 
    309     }
    310 
    311     private boolean parseOptimizationOption() {
    312       if (!acceptString("optimizations")) {
    313         return false;
    314       }
    315       skipWhitespace();
    316       do {
    317         skipOptimizationName();
    318         skipWhitespace();
    319       } while (acceptChar(','));
    320       return true;
    321     }
    322 
    323     private void skipOptimizationName() {
    324       if (acceptChar('!')) {
    325         skipWhitespace();
    326       }
    327       for (char next = peekChar();
    328           Character.isAlphabetic(next) || next == '/' || next == '*';
    329           next = peekChar()) {
    330         readChar();
    331       }
    332     }
    333 
    334     private void skipSingleArgument() {
    335       skipWhitespace();
    336       while (!eof() && !Character.isWhitespace(peekChar())) {
    337         readChar();
    338       }
    339     }
    340 
    341     private ProguardKeepRule parseKeepRule()
    342         throws ProguardRuleParserException {
    343       ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
    344       parseRuleTypeAndModifiers(keepRuleBuilder);
    345       parseClassSpec(keepRuleBuilder, false);
    346       return keepRuleBuilder.build();
    347     }
    348 
    349     private ProguardKeepRule parseWhyAreYouKeepingRule()
    350         throws ProguardRuleParserException {
    351       ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
    352       keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
    353       keepRuleBuilder.getModifiersBuilder().whyAreYouKeeping = true;
    354       keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
    355       parseClassSpec(keepRuleBuilder, false);
    356       return keepRuleBuilder.build();
    357     }
    358 
    359     private ProguardKeepRule parseKeepPackageNamesRule()
    360         throws ProguardRuleParserException {
    361       ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
    362       keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
    363       keepRuleBuilder.getModifiersBuilder().keepPackageNames = true;
    364       keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
    365       keepRuleBuilder.setClassNames(parseClassNames());
    366       return keepRuleBuilder.build();
    367     }
    368 
    369     private ProguardKeepRule parseCheckDiscardRule()
    370         throws ProguardRuleParserException {
    371       ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
    372       keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
    373       keepRuleBuilder.getModifiersBuilder().checkDiscarded = true;
    374       parseClassSpec(keepRuleBuilder, false);
    375       keepRuleBuilder.setType(keepRuleBuilder.getMemberRules().isEmpty() ? ProguardKeepRuleType.KEEP
    376           : ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
    377       return keepRuleBuilder.build();
    378     }
    379 
    380     private void parseClassSpec(
    381         ProguardConfigurationRule.Builder builder, boolean allowValueSpecification)
    382         throws ProguardRuleParserException {
    383       parseClassFlagsAndAnnotations(builder);
    384       builder.setClassType(parseClassType());
    385       builder.setClassNames(parseClassNames());
    386       parseInheritance(builder);
    387       parseMemberRules(builder, allowValueSpecification);
    388     }
    389 
    390     private void parseRuleTypeAndModifiers(ProguardKeepRule.Builder builder)
    391         throws ProguardRuleParserException {
    392       if (acceptString("names")) {
    393         builder.setType(ProguardKeepRuleType.KEEP);
    394         builder.getModifiersBuilder().allowsShrinking = true;
    395       } else if (acceptString("class")) {
    396         if (acceptString("members")) {
    397           builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
    398         } else if (acceptString("eswithmembers")) {
    399           builder.setType(ProguardKeepRuleType.KEEP_CLASSES_WITH_MEMBERS);
    400         } else if (acceptString("membernames")) {
    401           builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
    402           builder.getModifiersBuilder().allowsShrinking = true;
    403         } else if (acceptString("eswithmembernames")) {
    404           builder.setType(ProguardKeepRuleType.KEEP_CLASSES_WITH_MEMBERS);
    405           builder.getModifiersBuilder().allowsShrinking = true;
    406         } else {
    407           // The only path to here is through "-keep" followed by "class".
    408           unacceptString("-keepclass");
    409           throw parseError("Unknown option");
    410         }
    411       } else {
    412         builder.setType(ProguardKeepRuleType.KEEP);
    413       }
    414       parseRuleModifiers(builder);
    415     }
    416 
    417     private void parseRuleModifiers(ProguardKeepRule.Builder builder) {
    418       while (acceptChar(',')) {
    419         if (acceptString("allow")) {
    420           if (acceptString("shrinking")) {
    421             builder.getModifiersBuilder().allowsShrinking = true;
    422           } else if (acceptString("optimization")) {
    423             builder.getModifiersBuilder().allowsOptimization = true;
    424           } else if (acceptString("obfuscation")) {
    425             builder.getModifiersBuilder().allowsObfuscation = true;
    426           }
    427         } else if (acceptString("includedescriptorclasses")) {
    428           builder.getModifiersBuilder().includeDescriptorClasses = true;
    429         }
    430       }
    431     }
    432 
    433     private ProguardTypeMatcher parseAnnotation() throws ProguardRuleParserException {
    434       skipWhitespace();
    435       int startPosition = position;
    436       if (acceptChar('@')) {
    437         String className = parseClassName();
    438         if (className.equals("interface")) {
    439           // Not an annotation after all but a class type. Move position back to start
    440           // so this can be dealt with as a class type instead.
    441           position = startPosition;
    442           return null;
    443         }
    444         return ProguardTypeMatcher.create(className, ClassOrType.CLASS, dexItemFactory);
    445       }
    446       return null;
    447     }
    448 
    449     private boolean parseNegation() {
    450       skipWhitespace();
    451       return acceptChar('!');
    452     }
    453 
    454     private void parseClassFlagsAndAnnotations(ProguardClassSpecification.Builder builder)
    455         throws ProguardRuleParserException {
    456       while (true) {
    457         skipWhitespace();
    458         ProguardTypeMatcher annotation = parseAnnotation();
    459         if (annotation != null) {
    460           // TODO(ager): Should we only allow one annotation? It looks that way from the
    461           // proguard keep rule description, but that seems like a strange restriction?
    462           assert builder.getClassAnnotation() == null;
    463           builder.setClassAnnotation(annotation);
    464         } else {
    465           DexAccessFlags flags =
    466               parseNegation() ? builder.getNegatedClassAccessFlags() :
    467                 builder.getClassAccessFlags();
    468           skipWhitespace();
    469           if (acceptString("public")) {
    470             flags.setPublic();
    471           } else if (acceptString("final")) {
    472             flags.setFinal();
    473           } else if (acceptString("abstract")) {
    474             flags.setAbstract();
    475           } else {
    476             break;
    477           }
    478         }
    479       }
    480     }
    481 
    482     private ProguardClassType parseClassType() throws ProguardRuleParserException {
    483       skipWhitespace();
    484       if (acceptString("interface")) {
    485         return ProguardClassType.INTERFACE;
    486       } else if (acceptString("@interface")) {
    487         return ProguardClassType.ANNOTATION_INTERFACE;
    488       } else if (acceptString("class")) {
    489         return ProguardClassType.CLASS;
    490       } else if (acceptString("enum")) {
    491         return ProguardClassType.ENUM;
    492       } else {
    493         throw parseError("Expected interface|class|enum");
    494       }
    495     }
    496 
    497     private void parseInheritance(ProguardClassSpecification.Builder classSpecificationBuilder)
    498         throws ProguardRuleParserException {
    499       skipWhitespace();
    500       if (acceptString("implements")) {
    501         classSpecificationBuilder.setInheritanceIsExtends(false);
    502       } else if (acceptString("extends")) {
    503         classSpecificationBuilder.setInheritanceIsExtends(true);
    504       } else {
    505         return;
    506       }
    507       classSpecificationBuilder.setInheritanceAnnotation(parseAnnotation());
    508       classSpecificationBuilder.setInheritanceClassName(ProguardTypeMatcher.create(parseClassName(),
    509           ClassOrType.CLASS, dexItemFactory));
    510     }
    511 
    512     private void parseMemberRules(ProguardClassSpecification.Builder classSpecificationBuilder,
    513         boolean allowValueSpecification)
    514         throws ProguardRuleParserException {
    515       skipWhitespace();
    516       if (!eof() && acceptChar('{')) {
    517         ProguardMemberRule rule = null;
    518         while ((rule = parseMemberRule(allowValueSpecification)) != null) {
    519           classSpecificationBuilder.getMemberRules().add(rule);
    520         }
    521         skipWhitespace();
    522         expectChar('}');
    523       } else {
    524         // If there are no member rules, a default rule for the parameterless constructor
    525         // applies. So we add that here.
    526         ProguardMemberRule.Builder defaultRuleBuilder = ProguardMemberRule.builder();
    527         defaultRuleBuilder.setName(Constants.INSTANCE_INITIALIZER_NAME);
    528         defaultRuleBuilder.setRuleType(ProguardMemberType.INIT);
    529         defaultRuleBuilder.setArguments(Collections.emptyList());
    530         classSpecificationBuilder.getMemberRules().add(defaultRuleBuilder.build());
    531       }
    532     }
    533 
    534     private ProguardMemberRule parseMemberRule(boolean allowValueSpecification)
    535         throws ProguardRuleParserException {
    536       ProguardMemberRule.Builder ruleBuilder = ProguardMemberRule.builder();
    537       skipWhitespace();
    538       ruleBuilder.setAnnotation(parseAnnotation());
    539       parseMemberAccessFlags(ruleBuilder);
    540       parseMemberPattern(ruleBuilder, allowValueSpecification);
    541       return ruleBuilder.isValid() ? ruleBuilder.build() : null;
    542     }
    543 
    544     private void parseMemberAccessFlags(ProguardMemberRule.Builder ruleBuilder) {
    545       boolean found = true;
    546       while (found && !eof()) {
    547         found = false;
    548         DexAccessFlags flags =
    549             parseNegation() ? ruleBuilder.getNegatedAccessFlags() : ruleBuilder.getAccessFlags();
    550         skipWhitespace();
    551         switch (peekChar()) {
    552           case 'a':
    553             if (found = acceptString("abstract")) {
    554               flags.setAbstract();
    555             }
    556             break;
    557           case 'f':
    558             if (found = acceptString("final")) {
    559               flags.setFinal();
    560             }
    561             break;
    562           case 'n':
    563             if (found = acceptString("native")) {
    564               flags.setNative();
    565             }
    566             break;
    567           case 'p':
    568             if (found = acceptString("public")) {
    569               flags.setPublic();
    570             } else if (found = acceptString("private")) {
    571               flags.setPrivate();
    572             } else if (found = acceptString("protected")) {
    573               flags.setProtected();
    574             }
    575             break;
    576           case 's':
    577             if (found = acceptString("synchronized")) {
    578               flags.setSynchronized();
    579             } else if (found = acceptString("static")) {
    580               flags.setStatic();
    581             } else if (found = acceptString("strictfp")) {
    582               flags.setStrict();
    583             }
    584             break;
    585           case 't':
    586             if (found = acceptString("transient")) {
    587               flags.setTransient();
    588             }
    589             break;
    590           case 'v':
    591             if (found = acceptString("volatile")) {
    592               flags.setVolatile();
    593             }
    594             break;
    595         }
    596       }
    597     }
    598 
    599     private void parseMemberPattern(
    600         ProguardMemberRule.Builder ruleBuilder, boolean allowValueSpecification)
    601         throws ProguardRuleParserException {
    602       skipWhitespace();
    603       if (acceptString("<methods>")) {
    604         ruleBuilder.setRuleType(ProguardMemberType.ALL_METHODS);
    605       } else if (acceptString("<fields>")) {
    606         ruleBuilder.setRuleType(ProguardMemberType.ALL_FIELDS);
    607       } else if (acceptString("<init>")) {
    608         ruleBuilder.setRuleType(ProguardMemberType.INIT);
    609         ruleBuilder.setName("<init>");
    610         ruleBuilder.setArguments(parseArgumentList());
    611       } else {
    612         String first = acceptClassName();
    613         if (first != null) {
    614           skipWhitespace();
    615           if (first.equals("*") && hasNextChar(';')) {
    616             ruleBuilder.setRuleType(ProguardMemberType.ALL);
    617           } else {
    618             if (hasNextChar('(')) {
    619               ruleBuilder.setRuleType(ProguardMemberType.CONSTRUCTOR);
    620               ruleBuilder.setName(first);
    621               ruleBuilder.setArguments(parseArgumentList());
    622             } else {
    623               String second = acceptClassName();
    624               if (second != null) {
    625                 skipWhitespace();
    626                 if (hasNextChar('(')) {
    627                   ruleBuilder.setRuleType(ProguardMemberType.METHOD);
    628                   ruleBuilder.setName(second);
    629                   ruleBuilder
    630                       .setTypeMatcher(
    631                           ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
    632                   ruleBuilder.setArguments(parseArgumentList());
    633                 } else {
    634                   ruleBuilder.setRuleType(ProguardMemberType.FIELD);
    635                   ruleBuilder.setName(second);
    636                   ruleBuilder
    637                       .setTypeMatcher(
    638                           ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
    639                 }
    640                 skipWhitespace();
    641                 // Parse "return ..." if present.
    642                 if (acceptString("return")) {
    643                   skipWhitespace();
    644                   if (acceptString("true")) {
    645                     ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(true));
    646                   } else if (acceptString("false")) {
    647                     ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(false));
    648                   } else {
    649                     String qualifiedFieldName = acceptFieldName();
    650                     if (qualifiedFieldName != null) {
    651                       if (ruleBuilder.getTypeMatcher() instanceof MatchSpecificType) {
    652                         int lastDotIndex = qualifiedFieldName.lastIndexOf(".");
    653                         DexType fieldType = ((MatchSpecificType) ruleBuilder.getTypeMatcher()).type;
    654                         DexType fieldClass =
    655                             dexItemFactory.createType(
    656                                 DescriptorUtils.javaTypeToDescriptor(
    657                                     qualifiedFieldName.substring(0, lastDotIndex)));
    658                         DexString fieldName =
    659                             dexItemFactory.createString(
    660                                 qualifiedFieldName.substring(lastDotIndex + 1));
    661                         DexField field = dexItemFactory
    662                             .createField(fieldClass, fieldType, fieldName);
    663                         ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(field));
    664                       } else {
    665                         throw parseError("Expected specific type");
    666                       }
    667                     } else {
    668                       Integer min = acceptInteger();
    669                       Integer max = min;
    670                       if (min == null) {
    671                         throw parseError("Expected integer value");
    672                       }
    673                       skipWhitespace();
    674                       if (acceptString("..")) {
    675                         max = acceptInteger();
    676                         if (max == null) {
    677                           throw parseError("Expected integer value");
    678                         }
    679                       }
    680                       if (!allowValueSpecification) {
    681                         throw parseError("Unexpected value specification");
    682                       }
    683                       ruleBuilder.setReturnValue(
    684                           new ProguardMemberRuleReturnValue(new LongInterval(min, max)));
    685                     }
    686                   }
    687                 }
    688               } else {
    689                 throw parseError("Expected field or method name");
    690               }
    691             }
    692           }
    693         }
    694       }
    695       // If we found a member pattern eat the terminating ';'.
    696       if (ruleBuilder.isValid()) {
    697         skipWhitespace();
    698         expectChar(';');
    699       }
    700     }
    701 
    702     private List<ProguardTypeMatcher> parseArgumentList() throws ProguardRuleParserException {
    703       List<ProguardTypeMatcher> arguments = new ArrayList<>();
    704       skipWhitespace();
    705       expectChar('(');
    706       skipWhitespace();
    707       if (acceptChar(')')) {
    708         return arguments;
    709       }
    710       if (acceptString("...")) {
    711         arguments
    712             .add(ProguardTypeMatcher.create("...", ClassOrType.TYPE, dexItemFactory));
    713       } else {
    714         for (String name = parseClassName(); name != null; name =
    715             acceptChar(',') ? parseClassName() : null) {
    716           arguments
    717               .add(ProguardTypeMatcher.create(name, ClassOrType.TYPE, dexItemFactory));
    718           skipWhitespace();
    719         }
    720       }
    721       skipWhitespace();
    722       expectChar(')');
    723       return arguments;
    724     }
    725 
    726     private Path parseFileName() throws ProguardRuleParserException {
    727       skipWhitespace();
    728       int start = position;
    729       int end = position;
    730       while (!eof(end)) {
    731         char current = contents.charAt(end);
    732         if (current != File.pathSeparatorChar && !Character.isWhitespace(current)) {
    733           end++;
    734         } else {
    735           break;
    736         }
    737       }
    738       if (start == end) {
    739         throw parseError("File name expected");
    740       }
    741       position = end;
    742       return baseDirectory.resolve(contents.substring(start, end));
    743     }
    744 
    745     private List<Path> parseClassPath() throws ProguardRuleParserException {
    746       List<Path> classPath = new ArrayList<>();
    747       skipWhitespace();
    748       Path file = parseFileName();
    749       classPath.add(file);
    750       while (acceptChar(File.pathSeparatorChar)) {
    751         file = parseFileName();
    752         classPath.add(file);
    753       }
    754       return classPath;
    755     }
    756 
    757     private ProguardAssumeNoSideEffectRule parseAssumeNoSideEffectsRule()
    758         throws ProguardRuleParserException {
    759       ProguardAssumeNoSideEffectRule.Builder builder = ProguardAssumeNoSideEffectRule.builder();
    760       parseClassSpec(builder, true);
    761       return builder.build();
    762     }
    763 
    764     private ProguardAssumeValuesRule parseAssumeValuesRule() throws ProguardRuleParserException {
    765       ProguardAssumeValuesRule.Builder builder = ProguardAssumeValuesRule.builder();
    766       parseClassSpec(builder, true);
    767       return builder.build();
    768     }
    769 
    770     private void skipWhitespace() {
    771       while (!eof() && Character.isWhitespace(contents.charAt(position))) {
    772         position++;
    773       }
    774       skipComment();
    775     }
    776 
    777     private void skipComment() {
    778       if (eof()) {
    779         return;
    780       }
    781       if (peekChar() == '#') {
    782         while (!eof() && readChar() != '\n') {
    783           ;
    784         }
    785         skipWhitespace();
    786       }
    787     }
    788 
    789     private boolean eof() {
    790       return position == contents.length();
    791     }
    792 
    793     private boolean eof(int position) {
    794       return position == contents.length();
    795     }
    796 
    797     private boolean hasNextChar(char c) {
    798       if (eof()) {
    799         return false;
    800       }
    801       return peekChar() == c;
    802     }
    803 
    804     private boolean isOptionalArgumentGiven() {
    805       return !eof() && !hasNextChar('-');
    806     }
    807 
    808     private boolean acceptChar(char c) {
    809       if (hasNextChar(c)) {
    810         position++;
    811         return true;
    812       }
    813       return false;
    814     }
    815 
    816     private char peekChar() {
    817       return contents.charAt(position);
    818     }
    819 
    820     private char readChar() {
    821       return contents.charAt(position++);
    822     }
    823 
    824     private int remainingChars() {
    825       return contents.length() - position;
    826     }
    827 
    828     private void expectChar(char c) throws ProguardRuleParserException {
    829       if (eof() || readChar() != c) {
    830         throw parseError("Expected char '" + c + "'");
    831       }
    832     }
    833 
    834     private void expectString(String expected) throws ProguardRuleParserException {
    835       if (remainingChars() < expected.length()) {
    836         throw parseError("Expected string '" + expected + "'");
    837       }
    838       for (int i = 0; i < expected.length(); i++) {
    839         if (expected.charAt(i) != readChar()) {
    840           throw parseError("Expected string '" + expected + "'");
    841         }
    842       }
    843     }
    844 
    845     private boolean acceptString(String expected) {
    846       if (remainingChars() < expected.length()) {
    847         return false;
    848       }
    849       for (int i = 0; i < expected.length(); i++) {
    850         if (expected.charAt(i) != contents.charAt(position + i)) {
    851           return false;
    852         }
    853       }
    854       position += expected.length();
    855       return true;
    856     }
    857 
    858     private Integer acceptInteger() {
    859       skipWhitespace();
    860       int start = position;
    861       int end = position;
    862       while (!eof(end)) {
    863         char current = contents.charAt(end);
    864         if (Character.isDigit(current)) {
    865           end++;
    866         } else {
    867           break;
    868         }
    869       }
    870       if (start == end) {
    871         return null;
    872       }
    873       position = end;
    874       return Integer.parseInt(contents.substring(start, end));
    875     }
    876 
    877     private String acceptClassName() {
    878       skipWhitespace();
    879       int start = position;
    880       int end = position;
    881       while (!eof(end)) {
    882         char current = contents.charAt(end);
    883         if (Character.isJavaIdentifierPart(current) ||
    884             current == '.' ||
    885             current == '*' ||
    886             current == '?' ||
    887             current == '%' ||
    888             current == '[' ||
    889             current == ']') {
    890           end++;
    891         } else {
    892           break;
    893         }
    894       }
    895       if (start == end) {
    896         return null;
    897       }
    898       position = end;
    899       return contents.substring(start, end);
    900     }
    901 
    902     private String acceptFieldName() {
    903       skipWhitespace();
    904       int start = position;
    905       int end = position;
    906       while (!eof(end)) {
    907         char current = contents.charAt(end);
    908         if ((start == end && Character.isJavaIdentifierStart(current)) ||
    909             (start < end) && (Character.isJavaIdentifierPart(current) || current == '.')) {
    910           end++;
    911         } else {
    912           break;
    913         }
    914       }
    915       if (start == end) {
    916         return null;
    917       }
    918       position = end;
    919       return contents.substring(start, end);
    920     }
    921 
    922     private String acceptPatternList() {
    923       skipWhitespace();
    924       int start = position;
    925       int end = position;
    926       while (!eof(end)) {
    927         char current = contents.charAt(end);
    928         if (Character.isJavaIdentifierPart(current) ||
    929             current == '!' ||
    930             current == '*' ||
    931             current == ',') {
    932           end++;
    933         } else {
    934           break;
    935         }
    936       }
    937       if (start == end) {
    938         return null;
    939       }
    940       position = end;
    941       return contents.substring(start, end);
    942     }
    943 
    944     private void unacceptString(String expected) {
    945       assert position >= expected.length();
    946       position -= expected.length();
    947       for (int i = 0; i < expected.length(); i++) {
    948         assert expected.charAt(i) == contents.charAt(position + i);
    949       }
    950     }
    951 
    952     private void checkNotNegatedPattern() throws ProguardRuleParserException {
    953       skipWhitespace();
    954       if (acceptChar('!')) {
    955         throw parseError("Negated filters are not supported");
    956       }
    957     }
    958 
    959     private List<ProguardTypeMatcher> parseClassNames() throws ProguardRuleParserException {
    960       List<ProguardTypeMatcher> classNames = new ArrayList<>();
    961       checkNotNegatedPattern();
    962       classNames
    963           .add(ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
    964       skipWhitespace();
    965       while (acceptChar(',')) {
    966         checkNotNegatedPattern();
    967         classNames
    968             .add(ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
    969         skipWhitespace();
    970       }
    971       return classNames;
    972     }
    973 
    974     private String parsePackageNameOrEmptyString() {
    975       String name = acceptClassName();
    976       return name == null ? "" : name;
    977     }
    978 
    979     private String parseClassName() throws ProguardRuleParserException {
    980       String name = acceptClassName();
    981       if (name == null) {
    982         throw parseError("Class name expected");
    983       }
    984       return name;
    985     }
    986 
    987     private String snippetForPosition() {
    988       // TODO(ager): really should deal with \r as well to get column right.
    989       String[] lines = contents.split("\n", -1);  // -1 to get trailing empty lines represented.
    990       int remaining = position;
    991       for (int lineNumber = 0; lineNumber < lines.length; lineNumber++) {
    992         String line = lines[lineNumber];
    993         if (remaining <= line.length() || lineNumber == lines.length - 1) {
    994           String arrow = CharBuffer.allocate(remaining).toString().replace( '\0', ' ' ) + '^';
    995           return path.toString() + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
    996               + '\n' + arrow;
    997         }
    998         remaining -= (line.length() + 1); // Include newline.
    999       }
   1000       return path.toString();
   1001     }
   1002 
   1003     private ProguardRuleParserException parseError(String message) {
   1004       return new ProguardRuleParserException(message, snippetForPosition());
   1005     }
   1006 
   1007     private ProguardRuleParserException parseError(String message, Throwable cause) {
   1008       return new ProguardRuleParserException(message, snippetForPosition(), cause);
   1009     }
   1010   }
   1011 }
   1012