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