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