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