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