1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dx.command.dexer; 18 19 import com.android.dx.Version; 20 import com.android.dx.cf.iface.ParseException; 21 import com.android.dx.cf.direct.ClassPathOpener; 22 import com.android.dx.command.DxConsole; 23 import com.android.dx.command.UsageException; 24 import com.android.dx.dex.cf.CfOptions; 25 import com.android.dx.dex.cf.CfTranslator; 26 import com.android.dx.dex.cf.CodeStatistics; 27 import com.android.dx.dex.code.PositionList; 28 import com.android.dx.dex.file.ClassDefItem; 29 import com.android.dx.dex.file.DexFile; 30 import com.android.dx.dex.file.EncodedMethod; 31 import com.android.dx.rop.annotation.Annotation; 32 import com.android.dx.rop.annotation.Annotations; 33 import com.android.dx.rop.annotation.AnnotationsList; 34 import com.android.dx.rop.cst.CstNat; 35 import com.android.dx.rop.cst.CstUtf8; 36 37 import java.io.ByteArrayInputStream; 38 import java.io.File; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.io.OutputStream; 42 import java.io.OutputStreamWriter; 43 import java.io.PrintWriter; 44 import java.util.Arrays; 45 import java.util.ArrayList; 46 import java.util.Map; 47 import java.util.TreeMap; 48 import java.util.jar.Attributes; 49 import java.util.jar.JarEntry; 50 import java.util.jar.JarOutputStream; 51 import java.util.jar.Manifest; 52 53 /** 54 * Main class for the class file translator. 55 */ 56 public class Main { 57 /** 58 * {@code non-null;} name for the {@code .dex} file that goes into 59 * {@code .jar} files 60 */ 61 private static final String DEX_IN_JAR_NAME = "classes.dex"; 62 63 /** 64 * {@code non-null;} name of the standard manifest file in {@code .jar} 65 * files 66 */ 67 private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 68 69 /** 70 * {@code non-null;} attribute name for the (quasi-standard?) 71 * {@code Created-By} attribute 72 */ 73 private static final Attributes.Name CREATED_BY = 74 new Attributes.Name("Created-By"); 75 76 /** 77 * {@code non-null;} list of {@code javax} subpackages that are considered 78 * to be "core". <b>Note:</b>: This list must be sorted, since it 79 * is binary-searched. 80 */ 81 private static final String[] JAVAX_CORE = { 82 "accessibility", "crypto", "imageio", "management", "naming", "net", 83 "print", "rmi", "security", "sound", "sql", "swing", "transaction", 84 "xml" 85 }; 86 87 /** number of warnings during processing */ 88 private static int warnings = 0; 89 90 /** number of errors during processing */ 91 private static int errors = 0; 92 93 /** {@code non-null;} parsed command-line arguments */ 94 private static Arguments args; 95 96 /** {@code non-null;} output file in-progress */ 97 private static DexFile outputDex; 98 99 /** 100 * {@code null-ok;} map of resources to include in the output, or 101 * {@code null} if resources are being ignored 102 */ 103 private static TreeMap<String, byte[]> outputResources; 104 105 /** 106 * This class is uninstantiable. 107 */ Main()108 private Main() { 109 // This space intentionally left blank. 110 } 111 112 /** 113 * Run and exit if something unexpected happened. 114 * @param argArray the command line arguments 115 */ main(String[] argArray)116 public static void main(String[] argArray) { 117 Arguments arguments = new Arguments(); 118 arguments.parse(argArray); 119 120 int result = run(arguments); 121 if (result != 0) { 122 System.exit(result); 123 } 124 } 125 126 /** 127 * Run and return a result code. 128 * @param arguments the data + parameters for the conversion 129 * @return 0 if success > 0 otherwise. 130 */ run(Arguments arguments)131 public static int run(Arguments arguments) { 132 // Reset the error/warning count to start fresh. 133 warnings = 0; 134 errors = 0; 135 136 args = arguments; 137 args.makeCfOptions(); 138 139 if (!processAllFiles()) { 140 return 1; 141 } 142 143 byte[] outArray = writeDex(); 144 145 if (outArray == null) { 146 return 2; 147 } 148 149 if (args.jarOutput) { 150 // Effectively free up the (often massive) DexFile memory. 151 outputDex = null; 152 153 if (!createJar(args.outName, outArray)) { 154 return 3; 155 } 156 } 157 158 return 0; 159 } 160 161 /** 162 * Constructs the output {@link DexFile}, fill it in with all the 163 * specified classes, and populate the resources map if required. 164 * 165 * @return whether processing was successful 166 */ processAllFiles()167 private static boolean processAllFiles() { 168 outputDex = new DexFile(); 169 170 if (args.jarOutput) { 171 outputResources = new TreeMap<String, byte[]>(); 172 } 173 174 if (args.dumpWidth != 0) { 175 outputDex.setDumpWidth(args.dumpWidth); 176 } 177 178 boolean any = false; 179 String[] fileNames = args.fileNames; 180 181 try { 182 for (int i = 0; i < fileNames.length; i++) { 183 any |= processOne(fileNames[i]); 184 } 185 } catch (StopProcessing ex) { 186 /* 187 * Ignore it and just let the warning/error reporting do 188 * their things. 189 */ 190 } 191 192 if (warnings != 0) { 193 DxConsole.err.println(warnings + " warning" + 194 ((warnings == 1) ? "" : "s")); 195 } 196 197 if (errors != 0) { 198 DxConsole.err.println(errors + " error" + 199 ((errors == 1) ? "" : "s") + "; aborting"); 200 return false; 201 } 202 203 if (!(any || args.emptyOk)) { 204 DxConsole.err.println("no classfiles specified"); 205 return false; 206 } 207 208 if (args.optimize && args.statistics) { 209 CodeStatistics.dumpStatistics(DxConsole.out); 210 } 211 212 return true; 213 } 214 215 /** 216 * Processes one pathname element. 217 * 218 * @param pathname {@code non-null;} the pathname to process. May 219 * be the path of a class file, a jar file, or a directory 220 * containing class files. 221 * @return whether any processing actually happened 222 */ processOne(String pathname)223 private static boolean processOne(String pathname) { 224 ClassPathOpener opener; 225 226 opener = new ClassPathOpener(pathname, false, 227 new ClassPathOpener.Consumer() { 228 public boolean processFileBytes(String name, byte[] bytes) { 229 return Main.processFileBytes(name, bytes); 230 } 231 public void onException(Exception ex) { 232 if (ex instanceof StopProcessing) { 233 throw (StopProcessing) ex; 234 } 235 DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 236 ex.printStackTrace(DxConsole.err); 237 errors++; 238 } 239 public void onProcessArchiveStart(File file) { 240 if (args.verbose) { 241 DxConsole.out.println("processing archive " + file + 242 "..."); 243 } 244 } 245 }); 246 247 return opener.process(); 248 } 249 250 /** 251 * Processes one file, which may be either a class or a resource. 252 * 253 * @param name {@code non-null;} name of the file 254 * @param bytes {@code non-null;} contents of the file 255 * @return whether processing was successful 256 */ processFileBytes(String name, byte[] bytes)257 private static boolean processFileBytes(String name, byte[] bytes) { 258 boolean isClass = name.endsWith(".class"); 259 boolean keepResources = (outputResources != null); 260 261 if (!isClass && !keepResources) { 262 if (args.verbose) { 263 DxConsole.out.println("ignored resource " + name); 264 } 265 return false; 266 } 267 268 if (args.verbose) { 269 DxConsole.out.println("processing " + name + "..."); 270 } 271 272 String fixedName = fixPath(name); 273 274 if (isClass) { 275 if (keepResources && args.keepClassesInJar) { 276 outputResources.put(fixedName, bytes); 277 } 278 return processClass(fixedName, bytes); 279 } else { 280 outputResources.put(fixedName, bytes); 281 return true; 282 } 283 } 284 285 /** 286 * Processes one classfile. 287 * 288 * @param name {@code non-null;} name of the file, clipped such that it 289 * <i>should</i> correspond to the name of the class it contains 290 * @param bytes {@code non-null;} contents of the file 291 * @return whether processing was successful 292 */ processClass(String name, byte[] bytes)293 private static boolean processClass(String name, byte[] bytes) { 294 if (! args.coreLibrary) { 295 checkClassName(name); 296 } 297 298 try { 299 ClassDefItem clazz = 300 CfTranslator.translate(name, bytes, args.cfOptions); 301 outputDex.add(clazz); 302 return true; 303 } catch (ParseException ex) { 304 DxConsole.err.println("\ntrouble processing:"); 305 if (args.debug) { 306 ex.printStackTrace(DxConsole.err); 307 } else { 308 ex.printContext(DxConsole.err); 309 } 310 } 311 312 warnings++; 313 return false; 314 } 315 316 /** 317 * Check the class name to make sure it's not a "core library" 318 * class. If there is a problem, this updates the error count and 319 * throws an exception to stop processing. 320 * 321 * @param name {@code non-null;} the fully-qualified internal-form 322 * class name 323 */ checkClassName(String name)324 private static void checkClassName(String name) { 325 boolean bogus = false; 326 327 if (name.startsWith("java/")) { 328 bogus = true; 329 } else if (name.startsWith("javax/")) { 330 int slashAt = name.indexOf('/', 6); 331 if (slashAt == -1) { 332 // Top-level javax classes are verboten. 333 bogus = true; 334 } else { 335 String pkg = name.substring(6, slashAt); 336 bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0); 337 } 338 } 339 340 if (! bogus) { 341 return; 342 } 343 344 /* 345 * The user is probably trying to include an entire desktop 346 * core library in a misguided attempt to get their application 347 * working. Try to help them understand what's happening. 348 */ 349 350 DxConsole.err.println("\ntrouble processing \"" + name + "\":"); 351 DxConsole.err.println("\n" + 352 "Attempt to include a core class (java.* or javax.*) in " + 353 "something other\n" + 354 "than a core library. It is likely that you have " + 355 "attempted to include\n" + 356 "in an application the core library (or a part thereof) " + 357 "from a desktop\n" + 358 "virtual machine. This will most assuredly not work. " + 359 "At a minimum, it\n" + 360 "jeopardizes the compatibility of your app with future " + 361 "versions of the\n" + 362 "platform. It is also often of questionable legality.\n" + 363 "\n" + 364 "If you really intend to build a core library -- which is " + 365 "only\n" + 366 "appropriate as part of creating a full virtual machine " + 367 "distribution,\n" + 368 "as opposed to compiling an application -- then use the\n" + 369 "\"--core-library\" option to suppress this error message.\n" + 370 "\n" + 371 "If you go ahead and use \"--core-library\" but are in " + 372 "fact building an\n" + 373 "application, then be forewarned that your application " + 374 "will still fail\n" + 375 "to build or run, at some point. Please be prepared for " + 376 "angry customers\n" + 377 "who find, for example, that your application ceases to " + 378 "function once\n" + 379 "they upgrade their operating system. You will be to " + 380 "blame for this\n" + 381 "problem.\n" + 382 "\n" + 383 "If you are legitimately using some code that happens to " + 384 "be in a core\n" + 385 "package, then the easiest safe alternative you have is " + 386 "to repackage\n" + 387 "that code. That is, move the classes in question into " + 388 "your own package\n" + 389 "namespace. This means that they will never be in " + 390 "conflict with core\n" + 391 "system classes. If you find that you cannot do this, " + 392 "then that is an\n" + 393 "indication that the path you are on will ultimately lead " + 394 "to pain,\n" + 395 "suffering, grief, and lamentation.\n"); 396 errors++; 397 throw new StopProcessing(); 398 } 399 400 /** 401 * Converts {@link #outputDex} into a {@code byte[]}, write 402 * it out to the proper file (if any), and also do whatever human-oriented 403 * dumping is required. 404 * 405 * @return {@code null-ok;} the converted {@code byte[]} or {@code null} 406 * if there was a problem 407 */ writeDex()408 private static byte[] writeDex() { 409 byte[] outArray = null; 410 411 try { 412 OutputStream out = null; 413 OutputStream humanOutRaw = null; 414 OutputStreamWriter humanOut = null; 415 try { 416 if (args.humanOutName != null) { 417 humanOutRaw = openOutput(args.humanOutName); 418 humanOut = new OutputStreamWriter(humanOutRaw); 419 } 420 421 if (args.methodToDump != null) { 422 /* 423 * Simply dump the requested method. Note: The call 424 * to toDex() is required just to get the underlying 425 * structures ready. 426 */ 427 outputDex.toDex(null, false); 428 dumpMethod(outputDex, args.methodToDump, humanOut); 429 } else { 430 /* 431 * This is the usual case: Create an output .dex file, 432 * and write it, dump it, etc. 433 */ 434 outArray = outputDex.toDex(humanOut, args.verboseDump); 435 436 if ((args.outName != null) && !args.jarOutput) { 437 out = openOutput(args.outName); 438 out.write(outArray); 439 } 440 } 441 442 if (args.statistics) { 443 DxConsole.out.println(outputDex.getStatistics().toHuman()); 444 } 445 } finally { 446 if (humanOut != null) { 447 humanOut.flush(); 448 } 449 closeOutput(out); 450 closeOutput(humanOutRaw); 451 } 452 } catch (Exception ex) { 453 if (args.debug) { 454 DxConsole.err.println("\ntrouble writing output:"); 455 ex.printStackTrace(DxConsole.err); 456 } else { 457 DxConsole.err.println("\ntrouble writing output: " + 458 ex.getMessage()); 459 } 460 return null; 461 } 462 463 return outArray; 464 } 465 466 /** 467 * Creates a jar file from the resources and given dex file array. 468 * 469 * @param fileName {@code non-null;} name of the file 470 * @param dexArray {@code non-null;} array containing the dex file 471 * to include 472 * @return whether the creation was successful 473 */ createJar(String fileName, byte[] dexArray)474 private static boolean createJar(String fileName, byte[] dexArray) { 475 /* 476 * Make or modify the manifest (as appropriate), put the dex 477 * array into the resources map, and then process the entire 478 * resources map in a uniform manner. 479 */ 480 481 try { 482 Manifest manifest = makeManifest(); 483 OutputStream out = openOutput(fileName); 484 JarOutputStream jarOut = new JarOutputStream(out, manifest); 485 486 outputResources.put(DEX_IN_JAR_NAME, dexArray); 487 488 try { 489 for (Map.Entry<String, byte[]> e : 490 outputResources.entrySet()) { 491 String name = e.getKey(); 492 byte[] contents = e.getValue(); 493 JarEntry entry = new JarEntry(name); 494 495 if (args.verbose) { 496 DxConsole.out.println("writing " + name + "; size " + 497 contents.length + "..."); 498 } 499 500 entry.setSize(contents.length); 501 jarOut.putNextEntry(entry); 502 jarOut.write(contents); 503 jarOut.closeEntry(); 504 } 505 } finally { 506 jarOut.finish(); 507 jarOut.flush(); 508 closeOutput(out); 509 } 510 } catch (Exception ex) { 511 if (args.debug) { 512 DxConsole.err.println("\ntrouble writing output:"); 513 ex.printStackTrace(DxConsole.err); 514 } else { 515 DxConsole.err.println("\ntrouble writing output: " + 516 ex.getMessage()); 517 } 518 return false; 519 } 520 521 return true; 522 } 523 524 /** 525 * Creates and returns the manifest to use for the output. This may 526 * modify {@link #outputResources} (removing the pre-existing manifest). 527 * 528 * @return {@code non-null;} the manifest 529 */ makeManifest()530 private static Manifest makeManifest() throws IOException { 531 byte[] manifestBytes = outputResources.get(MANIFEST_NAME); 532 Manifest manifest; 533 Attributes attribs; 534 535 if (manifestBytes == null) { 536 // We need to construct an entirely new manifest. 537 manifest = new Manifest(); 538 attribs = manifest.getMainAttributes(); 539 attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 540 } else { 541 manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); 542 attribs = manifest.getMainAttributes(); 543 outputResources.remove(MANIFEST_NAME); 544 } 545 546 String createdBy = attribs.getValue(CREATED_BY); 547 if (createdBy == null) { 548 createdBy = ""; 549 } else { 550 createdBy += " + "; 551 } 552 createdBy += "dx " + Version.VERSION; 553 554 attribs.put(CREATED_BY, createdBy); 555 attribs.putValue("Dex-Location", DEX_IN_JAR_NAME); 556 557 return manifest; 558 } 559 560 /** 561 * Opens and returns the named file for writing, treating "-" specially. 562 * 563 * @param name {@code non-null;} the file name 564 * @return {@code non-null;} the opened file 565 */ openOutput(String name)566 private static OutputStream openOutput(String name) throws IOException { 567 if (name.equals("-") || 568 name.startsWith("-.")) { 569 return System.out; 570 } 571 572 return new FileOutputStream(name); 573 } 574 575 /** 576 * Flushes and closes the given output stream, except if it happens to be 577 * {@link System#out} in which case this method does the flush but not 578 * the close. This method will also silently do nothing if given a 579 * {@code null} argument. 580 * 581 * @param stream {@code null-ok;} what to close 582 */ closeOutput(OutputStream stream)583 private static void closeOutput(OutputStream stream) throws IOException { 584 if (stream == null) { 585 return; 586 } 587 588 stream.flush(); 589 590 if (stream != System.out) { 591 stream.close(); 592 } 593 } 594 595 /** 596 * Returns the "fixed" version of a given file path, suitable for 597 * use as a path within a {@code .jar} file and for checking 598 * against a classfile-internal "this class" name. This looks for 599 * the last instance of the substring {@code "/./"} within 600 * the path, and if it finds it, it takes the portion after to be 601 * the fixed path. If that isn't found but the path starts with 602 * {@code "./"}, then that prefix is removed and the rest is 603 * return. If neither of these is the case, this method returns 604 * its argument. 605 * 606 * @param path {@code non-null;} the path to "fix" 607 * @return {@code non-null;} the fixed version (which might be the same as 608 * the given {@code path}) 609 */ fixPath(String path)610 private static String fixPath(String path) { 611 /* 612 * If the path separator is \ (like on windows), we convert the 613 * path to a standard '/' separated path. 614 */ 615 if (File.separatorChar == '\\') { 616 path = path.replace('\\', '/'); 617 } 618 619 int index = path.lastIndexOf("/./"); 620 621 if (index != -1) { 622 return path.substring(index + 3); 623 } 624 625 if (path.startsWith("./")) { 626 return path.substring(2); 627 } 628 629 return path; 630 } 631 632 /** 633 * Dumps any method with the given name in the given file. 634 * 635 * @param dex {@code non-null;} the dex file 636 * @param fqName {@code non-null;} the fully-qualified name of the 637 * method(s) 638 * @param out {@code non-null;} where to dump to 639 */ dumpMethod(DexFile dex, String fqName, OutputStreamWriter out)640 private static void dumpMethod(DexFile dex, String fqName, 641 OutputStreamWriter out) { 642 boolean wildcard = fqName.endsWith("*"); 643 int lastDot = fqName.lastIndexOf('.'); 644 645 if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) { 646 DxConsole.err.println("bogus fully-qualified method name: " + 647 fqName); 648 return; 649 } 650 651 String className = fqName.substring(0, lastDot).replace('.', '/'); 652 String methodName = fqName.substring(lastDot + 1); 653 ClassDefItem clazz = dex.getClassOrNull(className); 654 655 if (clazz == null) { 656 DxConsole.err.println("no such class: " + className); 657 return; 658 } 659 660 if (wildcard) { 661 methodName = methodName.substring(0, methodName.length() - 1); 662 } 663 664 ArrayList<EncodedMethod> allMeths = clazz.getMethods(); 665 TreeMap<CstNat, EncodedMethod> meths = 666 new TreeMap<CstNat, EncodedMethod>(); 667 668 /* 669 * Figure out which methods to include in the output, and get them 670 * all sorted, so that the printout code is robust with respect to 671 * changes in the underlying order. 672 */ 673 for (EncodedMethod meth : allMeths) { 674 String methName = meth.getName().getString(); 675 if ((wildcard && methName.startsWith(methodName)) || 676 (!wildcard && methName.equals(methodName))) { 677 meths.put(meth.getRef().getNat(), meth); 678 } 679 } 680 681 if (meths.size() == 0) { 682 DxConsole.err.println("no such method: " + fqName); 683 return; 684 } 685 686 PrintWriter pw = new PrintWriter(out); 687 688 for (EncodedMethod meth : meths.values()) { 689 // TODO: Better stuff goes here, perhaps. 690 meth.debugPrint(pw, args.verboseDump); 691 692 /* 693 * The (default) source file is an attribute of the class, but 694 * it's useful to see it in method dumps. 695 */ 696 CstUtf8 sourceFile = clazz.getSourceFile(); 697 if (sourceFile != null) { 698 pw.println(" source file: " + sourceFile.toQuoted()); 699 } 700 701 Annotations methodAnnotations = 702 clazz.getMethodAnnotations(meth.getRef()); 703 AnnotationsList parameterAnnotations = 704 clazz.getParameterAnnotations(meth.getRef()); 705 706 if (methodAnnotations != null) { 707 pw.println(" method annotations:"); 708 for (Annotation a : methodAnnotations.getAnnotations()) { 709 pw.println(" " + a); 710 } 711 } 712 713 if (parameterAnnotations != null) { 714 pw.println(" parameter annotations:"); 715 int sz = parameterAnnotations.size(); 716 for (int i = 0; i < sz; i++) { 717 pw.println(" parameter " + i); 718 Annotations annotations = parameterAnnotations.get(i); 719 for (Annotation a : annotations.getAnnotations()) { 720 pw.println(" " + a); 721 } 722 } 723 } 724 } 725 726 pw.flush(); 727 } 728 729 /** 730 * Exception class used to halt processing prematurely. 731 */ 732 private static class StopProcessing extends RuntimeException { 733 // This space intentionally left blank. 734 } 735 736 /** 737 * Command-line argument parser and access. 738 */ 739 public static class Arguments { 740 /** whether to run in debug mode */ 741 public boolean debug = false; 742 743 /** whether to emit high-level verbose human-oriented output */ 744 public boolean verbose = false; 745 746 /** whether to emit verbose human-oriented output in the dump file */ 747 public boolean verboseDump = false; 748 749 /** whether we are constructing a core library */ 750 public boolean coreLibrary = false; 751 752 /** {@code null-ok;} particular method to dump */ 753 public String methodToDump = null; 754 755 /** max width for columnar output */ 756 public int dumpWidth = 0; 757 758 /** {@code null-ok;} output file name for binary file */ 759 public String outName = null; 760 761 /** {@code null-ok;} output file name for human-oriented dump */ 762 public String humanOutName = null; 763 764 /** whether strict file-name-vs-class-name checking should be done */ 765 public boolean strictNameCheck = true; 766 767 /** 768 * whether it is okay for there to be no {@code .class} files 769 * to process 770 */ 771 public boolean emptyOk = false; 772 773 /** 774 * whether the binary output is to be a {@code .jar} file 775 * instead of a plain {@code .dex} 776 */ 777 public boolean jarOutput = false; 778 779 /** 780 * when writing a {@code .jar} file, whether to still 781 * keep the {@code .class} files 782 */ 783 public boolean keepClassesInJar = false; 784 785 /** how much source position info to preserve */ 786 public int positionInfo = PositionList.LINES; 787 788 /** whether to keep local variable information */ 789 public boolean localInfo = true; 790 791 /** {@code non-null after {@link #parse};} file name arguments */ 792 public String[] fileNames; 793 794 /** whether to do SSA/register optimization */ 795 public boolean optimize = true; 796 797 /** Filename containg list of methods to optimize */ 798 public String optimizeListFile = null; 799 800 /** Filename containing list of methods to NOT optimize */ 801 public String dontOptimizeListFile = null; 802 803 /** Whether to print statistics to stdout at end of compile cycle */ 804 public boolean statistics; 805 806 /** Options for dex.cf.* */ 807 public CfOptions cfOptions; 808 809 /** 810 * Parses the given command-line arguments. 811 * 812 * @param args {@code non-null;} the arguments 813 */ parse(String[] args)814 public void parse(String[] args) { 815 int at = 0; 816 817 for (/*at*/; at < args.length; at++) { 818 String arg = args[at]; 819 if (arg.equals("--") || !arg.startsWith("--")) { 820 break; 821 } else if (arg.equals("--debug")) { 822 debug = true; 823 } else if (arg.equals("--verbose")) { 824 verbose = true; 825 } else if (arg.equals("--verbose-dump")) { 826 verboseDump = true; 827 } else if (arg.equals("--no-files")) { 828 emptyOk = true; 829 } else if (arg.equals("--no-optimize")) { 830 optimize = false; 831 } else if (arg.equals("--no-strict")) { 832 strictNameCheck = false; 833 } else if (arg.equals("--core-library")) { 834 coreLibrary = true; 835 } else if (arg.equals("--statistics")) { 836 statistics = true; 837 } else if (arg.startsWith("--optimize-list=")) { 838 if (dontOptimizeListFile != null) { 839 System.err.println("--optimize-list and " 840 + "--no-optimize-list are incompatible."); 841 throw new UsageException(); 842 } 843 optimize = true; 844 optimizeListFile = arg.substring(arg.indexOf('=') + 1); 845 } else if (arg.startsWith("--no-optimize-list=")) { 846 if (dontOptimizeListFile != null) { 847 System.err.println("--optimize-list and " 848 + "--no-optimize-list are incompatible."); 849 throw new UsageException(); 850 } 851 optimize = true; 852 dontOptimizeListFile = arg.substring(arg.indexOf('=') + 1); 853 } else if (arg.equals("--keep-classes")) { 854 keepClassesInJar = true; 855 } else if (arg.startsWith("--output=")) { 856 outName = arg.substring(arg.indexOf('=') + 1); 857 if (outName.endsWith(".zip") || 858 outName.endsWith(".jar") || 859 outName.endsWith(".apk")) { 860 jarOutput = true; 861 } else if (outName.endsWith(".dex") || 862 outName.equals("-")) { 863 jarOutput = false; 864 } else { 865 System.err.println("unknown output extension: " + 866 outName); 867 throw new UsageException(); 868 } 869 } else if (arg.startsWith("--dump-to=")) { 870 humanOutName = arg.substring(arg.indexOf('=') + 1); 871 } else if (arg.startsWith("--dump-width=")) { 872 arg = arg.substring(arg.indexOf('=') + 1); 873 dumpWidth = Integer.parseInt(arg); 874 } else if (arg.startsWith("--dump-method=")) { 875 methodToDump = arg.substring(arg.indexOf('=') + 1); 876 jarOutput = false; 877 } else if (arg.startsWith("--positions=")) { 878 String pstr = arg.substring(arg.indexOf('=') + 1).intern(); 879 if (pstr == "none") { 880 positionInfo = PositionList.NONE; 881 } else if (pstr == "important") { 882 positionInfo = PositionList.IMPORTANT; 883 } else if (pstr == "lines") { 884 positionInfo = PositionList.LINES; 885 } else { 886 System.err.println("unknown positions option: " + 887 pstr); 888 throw new UsageException(); 889 } 890 } else if (arg.equals("--no-locals")) { 891 localInfo = false; 892 } else { 893 System.err.println("unknown option: " + arg); 894 throw new UsageException(); 895 } 896 } 897 898 int fileCount = args.length - at; 899 900 if (fileCount == 0) { 901 if (!emptyOk) { 902 System.err.println("no input files specified"); 903 throw new UsageException(); 904 } 905 } else if (emptyOk) { 906 System.out.println("ignoring input files"); 907 at = 0; 908 fileCount = 0; 909 } 910 911 fileNames = new String[fileCount]; 912 System.arraycopy(args, at, fileNames, 0, fileCount); 913 914 if ((humanOutName == null) && (methodToDump != null)) { 915 humanOutName = "-"; 916 } 917 918 makeCfOptions(); 919 } 920 921 /** 922 * Copies relevent arguments over into a CfOptions instance. 923 */ makeCfOptions()924 private void makeCfOptions() { 925 cfOptions = new CfOptions(); 926 927 cfOptions.positionInfo = positionInfo; 928 cfOptions.localInfo = localInfo; 929 cfOptions.strictNameCheck = strictNameCheck; 930 cfOptions.optimize = optimize; 931 cfOptions.optimizeListFile = optimizeListFile; 932 cfOptions.dontOptimizeListFile = dontOptimizeListFile; 933 cfOptions.statistics = statistics; 934 cfOptions.warn = DxConsole.err; 935 } 936 } 937 } 938