1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dx.command.dexer; 18 19 import com.android.dex.Dex; 20 import com.android.dex.DexException; 21 import com.android.dex.DexFormat; 22 import com.android.dex.util.FileUtils; 23 import com.android.dx.Version; 24 import com.android.dx.cf.code.SimException; 25 import com.android.dx.cf.direct.ClassPathOpener; 26 import com.android.dx.cf.direct.ClassPathOpener.FileNameFilter; 27 import com.android.dx.cf.direct.DirectClassFile; 28 import com.android.dx.cf.direct.StdAttributeFactory; 29 import com.android.dx.cf.iface.ParseException; 30 import com.android.dx.command.DxConsole; 31 import com.android.dx.command.UsageException; 32 import com.android.dx.dex.DexOptions; 33 import com.android.dx.dex.cf.CfOptions; 34 import com.android.dx.dex.cf.CfTranslator; 35 import com.android.dx.dex.cf.CodeStatistics; 36 import com.android.dx.dex.code.PositionList; 37 import com.android.dx.dex.file.ClassDefItem; 38 import com.android.dx.dex.file.DexFile; 39 import com.android.dx.dex.file.EncodedMethod; 40 import com.android.dx.merge.CollisionPolicy; 41 import com.android.dx.merge.DexMerger; 42 import com.android.dx.rop.annotation.Annotation; 43 import com.android.dx.rop.annotation.Annotations; 44 import com.android.dx.rop.annotation.AnnotationsList; 45 import com.android.dx.rop.cst.CstNat; 46 import com.android.dx.rop.cst.CstString; 47 48 import java.io.BufferedReader; 49 import java.io.ByteArrayInputStream; 50 import java.io.ByteArrayOutputStream; 51 import java.io.File; 52 import java.io.FileOutputStream; 53 import java.io.FileReader; 54 import java.io.IOException; 55 import java.io.OutputStream; 56 import java.io.OutputStreamWriter; 57 import java.io.PrintWriter; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.Collection; 61 import java.util.HashMap; 62 import java.util.HashSet; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.Set; 66 import java.util.TreeMap; 67 import java.util.concurrent.Callable; 68 import java.util.concurrent.ExecutionException; 69 import java.util.concurrent.ExecutorService; 70 import java.util.concurrent.Executors; 71 import java.util.concurrent.Future; 72 import java.util.concurrent.ThreadFactory; 73 import java.util.concurrent.TimeUnit; 74 import java.util.concurrent.atomic.AtomicInteger; 75 import java.util.jar.Attributes; 76 import java.util.jar.JarEntry; 77 import java.util.jar.JarOutputStream; 78 import java.util.jar.Manifest; 79 80 /** 81 * Main class for the class file translator. 82 */ 83 public class Main { 84 85 /** 86 * File extension of a {@code .dex} file. 87 */ 88 private static final String DEX_EXTENSION = ".dex"; 89 90 /** 91 * File name prefix of a {@code .dex} file automatically loaded in an 92 * archive. 93 */ 94 private static final String DEX_PREFIX = "classes"; 95 96 /** 97 * {@code non-null;} the lengthy message that tries to discourage 98 * people from defining core classes in applications 99 */ 100 private static final String IN_RE_CORE_CLASSES = 101 "Ill-advised or mistaken usage of a core class (java.* or javax.*)\n" + 102 "when not building a core library.\n\n" + 103 "This is often due to inadvertently including a core library file\n" + 104 "in your application's project, when using an IDE (such as\n" + 105 "Eclipse). If you are sure you're not intentionally defining a\n" + 106 "core class, then this is the most likely explanation of what's\n" + 107 "going on.\n\n" + 108 "However, you might actually be trying to define a class in a core\n" + 109 "namespace, the source of which you may have taken, for example,\n" + 110 "from a non-Android virtual machine project. This will most\n" + 111 "assuredly not work. At a minimum, it jeopardizes the\n" + 112 "compatibility of your app with future versions of the platform.\n" + 113 "It is also often of questionable legality.\n\n" + 114 "If you really intend to build a core library -- which is only\n" + 115 "appropriate as part of creating a full virtual machine\n" + 116 "distribution, as opposed to compiling an application -- then use\n" + 117 "the \"--core-library\" option to suppress this error message.\n\n" + 118 "If you go ahead and use \"--core-library\" but are in fact\n" + 119 "building an application, then be forewarned that your application\n" + 120 "will still fail to build or run, at some point. Please be\n" + 121 "prepared for angry customers who find, for example, that your\n" + 122 "application ceases to function once they upgrade their operating\n" + 123 "system. You will be to blame for this problem.\n\n" + 124 "If you are legitimately using some code that happens to be in a\n" + 125 "core package, then the easiest safe alternative you have is to\n" + 126 "repackage that code. That is, move the classes in question into\n" + 127 "your own package namespace. This means that they will never be in\n" + 128 "conflict with core system classes. JarJar is a tool that may help\n" + 129 "you in this endeavor. If you find that you cannot do this, then\n" + 130 "that is an indication that the path you are on will ultimately\n" + 131 "lead to pain, suffering, grief, and lamentation.\n"; 132 133 /** 134 * {@code non-null;} name of the standard manifest file in {@code .jar} 135 * files 136 */ 137 private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 138 139 /** 140 * {@code non-null;} attribute name for the (quasi-standard?) 141 * {@code Created-By} attribute 142 */ 143 private static final Attributes.Name CREATED_BY = 144 new Attributes.Name("Created-By"); 145 146 /** 147 * {@code non-null;} list of {@code javax} subpackages that are considered 148 * to be "core". <b>Note:</b>: This list must be sorted, since it 149 * is binary-searched. 150 */ 151 private static final String[] JAVAX_CORE = { 152 "accessibility", "crypto", "imageio", "management", "naming", "net", 153 "print", "rmi", "security", "sip", "sound", "sql", "swing", 154 "transaction", "xml" 155 }; 156 157 /* Array.newInstance may be added by RopperMachine, 158 * ArrayIndexOutOfBoundsException.<init> may be added by EscapeAnalysis */ 159 private static final int MAX_METHOD_ADDED_DURING_DEX_CREATION = 2; 160 161 /* <primitive types box class>.TYPE */ 162 private static final int MAX_FIELD_ADDED_DURING_DEX_CREATION = 9; 163 164 /** number of errors during processing */ 165 private static AtomicInteger errors = new AtomicInteger(0); 166 167 /** {@code non-null;} parsed command-line arguments */ 168 private static Arguments args; 169 170 /** {@code non-null;} output file in-progress */ 171 private static DexFile outputDex; 172 173 /** 174 * {@code null-ok;} map of resources to include in the output, or 175 * {@code null} if resources are being ignored 176 */ 177 private static TreeMap<String, byte[]> outputResources; 178 179 /** Library .dex files to merge into the output .dex. */ 180 private static final List<byte[]> libraryDexBuffers = new ArrayList<byte[]>(); 181 182 /** thread pool object used for multi-threaded file processing */ 183 private static ExecutorService threadPool; 184 185 /** used to handle Errors for multi-threaded file processing */ 186 private static List<Future<Void>> parallelProcessorFutures; 187 188 /** true if any files are successfully processed */ 189 private static volatile boolean anyFilesProcessed; 190 191 /** class files older than this must be defined in the target dex file. */ 192 private static long minimumFileAge = 0; 193 194 private static Set<String> classesInMainDex = null; 195 196 private static List<byte[]> dexOutputArrays = new ArrayList<byte[]>(); 197 198 private static OutputStreamWriter humanOutWriter = null; 199 200 /** 201 * This class is uninstantiable. 202 */ Main()203 private Main() { 204 // This space intentionally left blank. 205 } 206 207 /** 208 * Run and exit if something unexpected happened. 209 * @param argArray the command line arguments 210 */ main(String[] argArray)211 public static void main(String[] argArray) throws IOException { 212 Arguments arguments = new Arguments(); 213 arguments.parse(argArray); 214 215 int result = run(arguments); 216 if (result != 0) { 217 System.exit(result); 218 } 219 } 220 221 /** 222 * Run and return a result code. 223 * @param arguments the data + parameters for the conversion 224 * @return 0 if success > 0 otherwise. 225 */ run(Arguments arguments)226 public static int run(Arguments arguments) throws IOException { 227 // Reset the error count to start fresh. 228 errors.set(0); 229 // empty the list, so that tools that load dx and keep it around 230 // for multiple runs don't reuse older buffers. 231 libraryDexBuffers.clear(); 232 233 args = arguments; 234 args.makeOptionsObjects(); 235 236 OutputStream humanOutRaw = null; 237 if (args.humanOutName != null) { 238 humanOutRaw = openOutput(args.humanOutName); 239 humanOutWriter = new OutputStreamWriter(humanOutRaw); 240 } 241 242 try { 243 if (args.multiDex) { 244 return runMultiDex(); 245 } else { 246 return runMonoDex(); 247 } 248 } finally { 249 closeOutput(humanOutRaw); 250 } 251 } 252 253 /** 254 * {@code non-null;} Error message for too many method/field/type ids. 255 */ getTooManyIdsErrorMessage()256 public static String getTooManyIdsErrorMessage() { 257 if (args.multiDex) { 258 return "The list of classes given in " + Arguments.MAIN_DEX_LIST_OPTION + 259 " is too big and does not fit in the main dex."; 260 } else { 261 return "You may try using " + Arguments.MULTI_DEX_OPTION + " option."; 262 } 263 } 264 runMonoDex()265 private static int runMonoDex() throws IOException { 266 267 File incrementalOutFile = null; 268 if (args.incremental) { 269 if (args.outName == null) { 270 System.err.println( 271 "error: no incremental output name specified"); 272 return -1; 273 } 274 incrementalOutFile = new File(args.outName); 275 if (incrementalOutFile.exists()) { 276 minimumFileAge = incrementalOutFile.lastModified(); 277 } 278 } 279 280 if (!processAllFiles()) { 281 return 1; 282 } 283 284 if (args.incremental && !anyFilesProcessed) { 285 return 0; // this was a no-op incremental build 286 } 287 288 // this array is null if no classes were defined 289 byte[] outArray = null; 290 291 if (!outputDex.isEmpty() || (args.humanOutName != null)) { 292 outArray = writeDex(); 293 294 if (outArray == null) { 295 return 2; 296 } 297 } 298 299 if (args.incremental) { 300 outArray = mergeIncremental(outArray, incrementalOutFile); 301 } 302 303 outArray = mergeLibraryDexBuffers(outArray); 304 305 if (args.jarOutput) { 306 // Effectively free up the (often massive) DexFile memory. 307 outputDex = null; 308 309 if (outArray != null) { 310 outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray); 311 } 312 if (!createJar(args.outName)) { 313 return 3; 314 } 315 } else if (outArray != null && args.outName != null) { 316 OutputStream out = openOutput(args.outName); 317 out.write(outArray); 318 closeOutput(out); 319 } 320 321 return 0; 322 } 323 runMultiDex()324 private static int runMultiDex() throws IOException { 325 326 assert !args.incremental; 327 assert args.numThreads == 1; 328 329 if (args.mainDexListFile != null) { 330 classesInMainDex = new HashSet<String>(); 331 readPathsFromFile(args.mainDexListFile, classesInMainDex); 332 } 333 334 if (!processAllFiles()) { 335 return 1; 336 } 337 338 if (!libraryDexBuffers.isEmpty()) { 339 throw new DexException("Library dex files are not supported in multi-dex mode"); 340 } 341 342 if (outputDex != null) { 343 // this array is null if no classes were defined 344 dexOutputArrays.add(writeDex()); 345 346 // Effectively free up the (often massive) DexFile memory. 347 outputDex = null; 348 } 349 350 if (args.jarOutput) { 351 352 for (int i = 0; i < dexOutputArrays.size(); i++) { 353 outputResources.put(getDexFileName(i), 354 dexOutputArrays.get(i)); 355 } 356 357 if (!createJar(args.outName)) { 358 return 3; 359 } 360 } else if (args.outName != null) { 361 File outDir = new File(args.outName); 362 assert outDir.isDirectory(); 363 for (int i = 0; i < dexOutputArrays.size(); i++) { 364 OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i))); 365 try { 366 out.write(dexOutputArrays.get(i)); 367 } finally { 368 closeOutput(out); 369 } 370 } 371 372 } 373 374 return 0; 375 } 376 getDexFileName(int i)377 private static String getDexFileName(int i) { 378 if (i == 0) { 379 return DexFormat.DEX_IN_JAR_NAME; 380 } else { 381 return DEX_PREFIX + (i + 1) + DEX_EXTENSION; 382 } 383 } 384 readPathsFromFile(String fileName, Collection<String> paths)385 private static void readPathsFromFile(String fileName, Collection<String> paths) throws IOException { 386 BufferedReader bfr = null; 387 try { 388 FileReader fr = new FileReader(fileName); 389 bfr = new BufferedReader(fr); 390 391 String line; 392 393 while (null != (line = bfr.readLine())) { 394 paths.add(fixPath(line)); 395 } 396 397 } finally { 398 if (bfr != null) { 399 bfr.close(); 400 } 401 } 402 } 403 404 /** 405 * Merges the dex files {@code update} and {@code base}, preferring 406 * {@code update}'s definition for types defined in both dex files. 407 * 408 * @param base a file to find the previous dex file. May be a .dex file, a 409 * jar file possibly containing a .dex file, or null. 410 * @return the bytes of the merged dex file, or null if both the update 411 * and the base dex do not exist. 412 */ mergeIncremental(byte[] update, File base)413 private static byte[] mergeIncremental(byte[] update, File base) throws IOException { 414 Dex dexA = null; 415 Dex dexB = null; 416 417 if (update != null) { 418 dexA = new Dex(update); 419 } 420 421 if (base.exists()) { 422 dexB = new Dex(base); 423 } 424 425 Dex result; 426 if (dexA == null && dexB == null) { 427 return null; 428 } else if (dexA == null) { 429 result = dexB; 430 } else if (dexB == null) { 431 result = dexA; 432 } else { 433 result = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge(); 434 } 435 436 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 437 result.writeTo(bytesOut); 438 return bytesOut.toByteArray(); 439 } 440 441 /** 442 * Merges the dex files in library jars. If multiple dex files define the 443 * same type, this fails with an exception. 444 */ mergeLibraryDexBuffers(byte[] outArray)445 private static byte[] mergeLibraryDexBuffers(byte[] outArray) throws IOException { 446 for (byte[] libraryDex : libraryDexBuffers) { 447 if (outArray == null) { 448 outArray = libraryDex; 449 continue; 450 } 451 452 Dex a = new Dex(outArray); 453 Dex b = new Dex(libraryDex); 454 Dex ab = new DexMerger(a, b, CollisionPolicy.FAIL).merge(); 455 outArray = ab.getBytes(); 456 } 457 458 return outArray; 459 } 460 461 /** 462 * Constructs the output {@link DexFile}, fill it in with all the 463 * specified classes, and populate the resources map if required. 464 * 465 * @return whether processing was successful 466 */ processAllFiles()467 private static boolean processAllFiles() { 468 createDexFile(); 469 470 if (args.jarOutput) { 471 outputResources = new TreeMap<String, byte[]>(); 472 } 473 474 anyFilesProcessed = false; 475 String[] fileNames = args.fileNames; 476 477 if (args.numThreads > 1) { 478 threadPool = Executors.newFixedThreadPool(args.numThreads); 479 parallelProcessorFutures = new ArrayList<Future<Void>>(); 480 } 481 482 try { 483 if (args.mainDexListFile != null) { 484 // with --main-dex-list 485 FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() : 486 new BestEffortMainDexListFilter(); 487 488 // forced in main dex 489 for (int i = 0; i < fileNames.length; i++) { 490 processOne(fileNames[i], mainPassFilter); 491 } 492 493 if (dexOutputArrays.size() > 0) { 494 throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION 495 + ", main dex capacity exceeded"); 496 } 497 498 if (args.minimalMainDex) { 499 // start second pass directly in a secondary dex file. 500 createDexFile(); 501 } 502 503 // remaining files 504 for (int i = 0; i < fileNames.length; i++) { 505 processOne(fileNames[i], new NotFilter(mainPassFilter)); 506 } 507 } else { 508 // without --main-dex-list 509 for (int i = 0; i < fileNames.length; i++) { 510 processOne(fileNames[i], ClassPathOpener.acceptAll); 511 } 512 } 513 } catch (StopProcessing ex) { 514 /* 515 * Ignore it and just let the error reporting do 516 * their things. 517 */ 518 } 519 520 if (args.numThreads > 1) { 521 try { 522 threadPool.shutdown(); 523 if (!threadPool.awaitTermination(600L, TimeUnit.SECONDS)) { 524 throw new RuntimeException("Timed out waiting for threads."); 525 } 526 } catch (InterruptedException ex) { 527 threadPool.shutdownNow(); 528 throw new RuntimeException("A thread has been interrupted."); 529 } 530 531 try { 532 for (Future<?> future : parallelProcessorFutures) { 533 future.get(); 534 } 535 } catch (ExecutionException e) { 536 Throwable cause = e.getCause(); 537 // All Exceptions should have been handled in the ParallelProcessor, only Errors 538 // should remain 539 if (cause instanceof Error) { 540 throw (Error) e.getCause(); 541 } else { 542 throw new AssertionError(e.getCause()); 543 } 544 } catch (InterruptedException e) { 545 // If we're here, it means all threads have completed cleanly, so there should not be 546 // any InterruptedException 547 throw new AssertionError(e); 548 } 549 } 550 551 int errorNum = errors.get(); 552 if (errorNum != 0) { 553 DxConsole.err.println(errorNum + " error" + 554 ((errorNum == 1) ? "" : "s") + "; aborting"); 555 return false; 556 } 557 558 if (args.incremental && !anyFilesProcessed) { 559 return true; 560 } 561 562 if (!(anyFilesProcessed || args.emptyOk)) { 563 DxConsole.err.println("no classfiles specified"); 564 return false; 565 } 566 567 if (args.optimize && args.statistics) { 568 CodeStatistics.dumpStatistics(DxConsole.out); 569 } 570 571 return true; 572 } 573 createDexFile()574 private static void createDexFile() { 575 if (outputDex != null) { 576 dexOutputArrays.add(writeDex()); 577 } 578 579 outputDex = new DexFile(args.dexOptions); 580 581 if (args.dumpWidth != 0) { 582 outputDex.setDumpWidth(args.dumpWidth); 583 } 584 } 585 586 /** 587 * Processes one pathname element. 588 * 589 * @param pathname {@code non-null;} the pathname to process. May 590 * be the path of a class file, a jar file, or a directory 591 * containing class files. 592 * @param filter {@code non-null;} A filter for excluding files. 593 */ processOne(String pathname, FileNameFilter filter)594 private static void processOne(String pathname, FileNameFilter filter) { 595 ClassPathOpener opener; 596 597 opener = new ClassPathOpener(pathname, false, filter, 598 new ClassPathOpener.Consumer() { 599 600 @Override 601 public boolean processFileBytes(String name, long lastModified, byte[] bytes) { 602 return Main.processFileBytes(name, lastModified, bytes); 603 } 604 605 @Override 606 public void onException(Exception ex) { 607 if (ex instanceof StopProcessing) { 608 throw (StopProcessing) ex; 609 } else if (ex instanceof SimException) { 610 DxConsole.err.println("\nEXCEPTION FROM SIMULATION:"); 611 DxConsole.err.println(ex.getMessage() + "\n"); 612 DxConsole.err.println(((SimException) ex).getContext()); 613 } else { 614 DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 615 ex.printStackTrace(DxConsole.err); 616 } 617 errors.incrementAndGet(); 618 } 619 620 @Override 621 public void onProcessArchiveStart(File file) { 622 if (args.verbose) { 623 DxConsole.out.println("processing archive " + file + 624 "..."); 625 } 626 } 627 }); 628 629 if (args.numThreads > 1) { 630 parallelProcessorFutures.add(threadPool.submit(new ParallelProcessor(opener))); 631 } else { 632 if (opener.process()) { 633 anyFilesProcessed = true; 634 } 635 } 636 } 637 638 /** 639 * Processes one file, which may be either a class or a resource. 640 * 641 * @param name {@code non-null;} name of the file 642 * @param bytes {@code non-null;} contents of the file 643 * @return whether processing was successful 644 */ processFileBytes(String name, long lastModified, byte[] bytes)645 private static boolean processFileBytes(String name, long lastModified, byte[] bytes) { 646 boolean isClass = name.endsWith(".class"); 647 boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME); 648 boolean keepResources = (outputResources != null); 649 650 if (!isClass && !isClassesDex && !keepResources) { 651 if (args.verbose) { 652 DxConsole.out.println("ignored resource " + name); 653 } 654 return false; 655 } 656 657 if (args.verbose) { 658 DxConsole.out.println("processing " + name + "..."); 659 } 660 661 String fixedName = fixPath(name); 662 663 if (isClass) { 664 665 if (keepResources && args.keepClassesInJar) { 666 synchronized (outputResources) { 667 outputResources.put(fixedName, bytes); 668 } 669 } 670 if (lastModified < minimumFileAge) { 671 return true; 672 } 673 return processClass(fixedName, bytes); 674 } else if (isClassesDex) { 675 synchronized (libraryDexBuffers) { 676 libraryDexBuffers.add(bytes); 677 } 678 return true; 679 } else { 680 synchronized (outputResources) { 681 outputResources.put(fixedName, bytes); 682 } 683 return true; 684 } 685 } 686 687 /** 688 * Processes one classfile. 689 * 690 * @param name {@code non-null;} name of the file, clipped such that it 691 * <i>should</i> correspond to the name of the class it contains 692 * @param bytes {@code non-null;} contents of the file 693 * @return whether processing was successful 694 */ processClass(String name, byte[] bytes)695 private static boolean processClass(String name, byte[] bytes) { 696 if (! args.coreLibrary) { 697 checkClassName(name); 698 } 699 700 DirectClassFile cf = 701 new DirectClassFile(bytes, name, args.cfOptions.strictNameCheck); 702 703 cf.setAttributeFactory(StdAttributeFactory.THE_ONE); 704 cf.getMagic(); 705 706 int numMethodIds = outputDex.getMethodIds().items().size(); 707 int numFieldIds = outputDex.getFieldIds().items().size(); 708 int constantPoolSize = cf.getConstantPool().size(); 709 710 int maxMethodIdsInDex = numMethodIds + constantPoolSize + cf.getMethods().size() + 711 MAX_METHOD_ADDED_DURING_DEX_CREATION; 712 int maxFieldIdsInDex = numFieldIds + constantPoolSize + cf.getFields().size() + 713 MAX_FIELD_ADDED_DURING_DEX_CREATION; 714 715 if (args.multiDex 716 // Never switch to the next dex if current dex is already empty 717 && (outputDex.getClassDefs().items().size() > 0) 718 && ((maxMethodIdsInDex > args.maxNumberOfIdxPerDex) || 719 (maxFieldIdsInDex > args.maxNumberOfIdxPerDex))) { 720 DexFile completeDex = outputDex; 721 createDexFile(); 722 assert (completeDex.getMethodIds().items().size() <= numMethodIds + 723 MAX_METHOD_ADDED_DURING_DEX_CREATION) && 724 (completeDex.getFieldIds().items().size() <= numFieldIds + 725 MAX_FIELD_ADDED_DURING_DEX_CREATION); 726 } 727 728 try { 729 ClassDefItem clazz = 730 CfTranslator.translate(cf, bytes, args.cfOptions, args.dexOptions, outputDex); 731 synchronized (outputDex) { 732 outputDex.add(clazz); 733 } 734 return true; 735 736 } catch (ParseException ex) { 737 DxConsole.err.println("\ntrouble processing:"); 738 if (args.debug) { 739 ex.printStackTrace(DxConsole.err); 740 } else { 741 ex.printContext(DxConsole.err); 742 } 743 } 744 errors.incrementAndGet(); 745 return false; 746 } 747 748 /** 749 * Check the class name to make sure it's not a "core library" 750 * class. If there is a problem, this updates the error count and 751 * throws an exception to stop processing. 752 * 753 * @param name {@code non-null;} the fully-qualified internal-form 754 * class name 755 */ checkClassName(String name)756 private static void checkClassName(String name) { 757 boolean bogus = false; 758 759 if (name.startsWith("java/")) { 760 bogus = true; 761 } else if (name.startsWith("javax/")) { 762 int slashAt = name.indexOf('/', 6); 763 if (slashAt == -1) { 764 // Top-level javax classes are verboten. 765 bogus = true; 766 } else { 767 String pkg = name.substring(6, slashAt); 768 bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0); 769 } 770 } 771 772 if (! bogus) { 773 return; 774 } 775 776 /* 777 * The user is probably trying to include an entire desktop 778 * core library in a misguided attempt to get their application 779 * working. Try to help them understand what's happening. 780 */ 781 782 DxConsole.err.println("\ntrouble processing \"" + name + "\":\n\n" + 783 IN_RE_CORE_CLASSES); 784 errors.incrementAndGet(); 785 throw new StopProcessing(); 786 } 787 788 /** 789 * Converts {@link #outputDex} into a {@code byte[]} and do whatever 790 * human-oriented dumping is required. 791 * 792 * @return {@code null-ok;} the converted {@code byte[]} or {@code null} 793 * if there was a problem 794 */ writeDex()795 private static byte[] writeDex() { 796 byte[] outArray = null; 797 798 try { 799 try { 800 if (args.methodToDump != null) { 801 /* 802 * Simply dump the requested method. Note: The call 803 * to toDex() is required just to get the underlying 804 * structures ready. 805 */ 806 outputDex.toDex(null, false); 807 dumpMethod(outputDex, args.methodToDump, humanOutWriter); 808 } else { 809 /* 810 * This is the usual case: Create an output .dex file, 811 * and write it, dump it, etc. 812 */ 813 outArray = outputDex.toDex(humanOutWriter, args.verboseDump); 814 } 815 816 if (args.statistics) { 817 DxConsole.out.println(outputDex.getStatistics().toHuman()); 818 } 819 } finally { 820 if (humanOutWriter != null) { 821 humanOutWriter.flush(); 822 } 823 } 824 } catch (Exception ex) { 825 if (args.debug) { 826 DxConsole.err.println("\ntrouble writing output:"); 827 ex.printStackTrace(DxConsole.err); 828 } else { 829 DxConsole.err.println("\ntrouble writing output: " + 830 ex.getMessage()); 831 } 832 return null; 833 } 834 835 return outArray; 836 } 837 838 /** 839 * Creates a jar file from the resources (including dex file arrays). 840 * 841 * @param fileName {@code non-null;} name of the file 842 * @return whether the creation was successful 843 */ createJar(String fileName)844 private static boolean createJar(String fileName) { 845 /* 846 * Make or modify the manifest (as appropriate), put the dex 847 * array into the resources map, and then process the entire 848 * resources map in a uniform manner. 849 */ 850 851 try { 852 Manifest manifest = makeManifest(); 853 OutputStream out = openOutput(fileName); 854 JarOutputStream jarOut = new JarOutputStream(out, manifest); 855 856 try { 857 for (Map.Entry<String, byte[]> e : 858 outputResources.entrySet()) { 859 String name = e.getKey(); 860 byte[] contents = e.getValue(); 861 JarEntry entry = new JarEntry(name); 862 int length = contents.length; 863 864 if (args.verbose) { 865 DxConsole.out.println("writing " + name + "; size " + length + "..."); 866 } 867 868 entry.setSize(length); 869 jarOut.putNextEntry(entry); 870 jarOut.write(contents); 871 jarOut.closeEntry(); 872 } 873 } finally { 874 jarOut.finish(); 875 jarOut.flush(); 876 closeOutput(out); 877 } 878 } catch (Exception ex) { 879 if (args.debug) { 880 DxConsole.err.println("\ntrouble writing output:"); 881 ex.printStackTrace(DxConsole.err); 882 } else { 883 DxConsole.err.println("\ntrouble writing output: " + 884 ex.getMessage()); 885 } 886 return false; 887 } 888 889 return true; 890 } 891 892 /** 893 * Creates and returns the manifest to use for the output. This may 894 * modify {@link #outputResources} (removing the pre-existing manifest). 895 * 896 * @return {@code non-null;} the manifest 897 */ makeManifest()898 private static Manifest makeManifest() throws IOException { 899 byte[] manifestBytes = outputResources.get(MANIFEST_NAME); 900 Manifest manifest; 901 Attributes attribs; 902 903 if (manifestBytes == null) { 904 // We need to construct an entirely new manifest. 905 manifest = new Manifest(); 906 attribs = manifest.getMainAttributes(); 907 attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 908 } else { 909 manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); 910 attribs = manifest.getMainAttributes(); 911 outputResources.remove(MANIFEST_NAME); 912 } 913 914 String createdBy = attribs.getValue(CREATED_BY); 915 if (createdBy == null) { 916 createdBy = ""; 917 } else { 918 createdBy += " + "; 919 } 920 createdBy += "dx " + Version.VERSION; 921 922 attribs.put(CREATED_BY, createdBy); 923 attribs.putValue("Dex-Location", DexFormat.DEX_IN_JAR_NAME); 924 925 return manifest; 926 } 927 928 /** 929 * Opens and returns the named file for writing, treating "-" specially. 930 * 931 * @param name {@code non-null;} the file name 932 * @return {@code non-null;} the opened file 933 */ openOutput(String name)934 private static OutputStream openOutput(String name) throws IOException { 935 if (name.equals("-") || 936 name.startsWith("-.")) { 937 return System.out; 938 } 939 940 return new FileOutputStream(name); 941 } 942 943 /** 944 * Flushes and closes the given output stream, except if it happens to be 945 * {@link System#out} in which case this method does the flush but not 946 * the close. This method will also silently do nothing if given a 947 * {@code null} argument. 948 * 949 * @param stream {@code null-ok;} what to close 950 */ closeOutput(OutputStream stream)951 private static void closeOutput(OutputStream stream) throws IOException { 952 if (stream == null) { 953 return; 954 } 955 956 stream.flush(); 957 958 if (stream != System.out) { 959 stream.close(); 960 } 961 } 962 963 /** 964 * Returns the "fixed" version of a given file path, suitable for 965 * use as a path within a {@code .jar} file and for checking 966 * against a classfile-internal "this class" name. This looks for 967 * the last instance of the substring {@code "/./"} within 968 * the path, and if it finds it, it takes the portion after to be 969 * the fixed path. If that isn't found but the path starts with 970 * {@code "./"}, then that prefix is removed and the rest is 971 * return. If neither of these is the case, this method returns 972 * its argument. 973 * 974 * @param path {@code non-null;} the path to "fix" 975 * @return {@code non-null;} the fixed version (which might be the same as 976 * the given {@code path}) 977 */ fixPath(String path)978 private static String fixPath(String path) { 979 /* 980 * If the path separator is \ (like on windows), we convert the 981 * path to a standard '/' separated path. 982 */ 983 if (File.separatorChar == '\\') { 984 path = path.replace('\\', '/'); 985 } 986 987 int index = path.lastIndexOf("/./"); 988 989 if (index != -1) { 990 return path.substring(index + 3); 991 } 992 993 if (path.startsWith("./")) { 994 return path.substring(2); 995 } 996 997 return path; 998 } 999 1000 /** 1001 * Dumps any method with the given name in the given file. 1002 * 1003 * @param dex {@code non-null;} the dex file 1004 * @param fqName {@code non-null;} the fully-qualified name of the 1005 * method(s) 1006 * @param out {@code non-null;} where to dump to 1007 */ dumpMethod(DexFile dex, String fqName, OutputStreamWriter out)1008 private static void dumpMethod(DexFile dex, String fqName, 1009 OutputStreamWriter out) { 1010 boolean wildcard = fqName.endsWith("*"); 1011 int lastDot = fqName.lastIndexOf('.'); 1012 1013 if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) { 1014 DxConsole.err.println("bogus fully-qualified method name: " + 1015 fqName); 1016 return; 1017 } 1018 1019 String className = fqName.substring(0, lastDot).replace('.', '/'); 1020 String methodName = fqName.substring(lastDot + 1); 1021 ClassDefItem clazz = dex.getClassOrNull(className); 1022 1023 if (clazz == null) { 1024 DxConsole.err.println("no such class: " + className); 1025 return; 1026 } 1027 1028 if (wildcard) { 1029 methodName = methodName.substring(0, methodName.length() - 1); 1030 } 1031 1032 ArrayList<EncodedMethod> allMeths = clazz.getMethods(); 1033 TreeMap<CstNat, EncodedMethod> meths = 1034 new TreeMap<CstNat, EncodedMethod>(); 1035 1036 /* 1037 * Figure out which methods to include in the output, and get them 1038 * all sorted, so that the printout code is robust with respect to 1039 * changes in the underlying order. 1040 */ 1041 for (EncodedMethod meth : allMeths) { 1042 String methName = meth.getName().getString(); 1043 if ((wildcard && methName.startsWith(methodName)) || 1044 (!wildcard && methName.equals(methodName))) { 1045 meths.put(meth.getRef().getNat(), meth); 1046 } 1047 } 1048 1049 if (meths.size() == 0) { 1050 DxConsole.err.println("no such method: " + fqName); 1051 return; 1052 } 1053 1054 PrintWriter pw = new PrintWriter(out); 1055 1056 for (EncodedMethod meth : meths.values()) { 1057 // TODO: Better stuff goes here, perhaps. 1058 meth.debugPrint(pw, args.verboseDump); 1059 1060 /* 1061 * The (default) source file is an attribute of the class, but 1062 * it's useful to see it in method dumps. 1063 */ 1064 CstString sourceFile = clazz.getSourceFile(); 1065 if (sourceFile != null) { 1066 pw.println(" source file: " + sourceFile.toQuoted()); 1067 } 1068 1069 Annotations methodAnnotations = 1070 clazz.getMethodAnnotations(meth.getRef()); 1071 AnnotationsList parameterAnnotations = 1072 clazz.getParameterAnnotations(meth.getRef()); 1073 1074 if (methodAnnotations != null) { 1075 pw.println(" method annotations:"); 1076 for (Annotation a : methodAnnotations.getAnnotations()) { 1077 pw.println(" " + a); 1078 } 1079 } 1080 1081 if (parameterAnnotations != null) { 1082 pw.println(" parameter annotations:"); 1083 int sz = parameterAnnotations.size(); 1084 for (int i = 0; i < sz; i++) { 1085 pw.println(" parameter " + i); 1086 Annotations annotations = parameterAnnotations.get(i); 1087 for (Annotation a : annotations.getAnnotations()) { 1088 pw.println(" " + a); 1089 } 1090 } 1091 } 1092 } 1093 1094 pw.flush(); 1095 } 1096 1097 private static class NotFilter implements FileNameFilter { 1098 private final FileNameFilter filter; 1099 NotFilter(FileNameFilter filter)1100 private NotFilter(FileNameFilter filter) { 1101 this.filter = filter; 1102 } 1103 1104 @Override accept(String path)1105 public boolean accept(String path) { 1106 return !filter.accept(path); 1107 } 1108 } 1109 1110 /** 1111 * A quick and accurate filter for when file path can be trusted. 1112 */ 1113 private static class MainDexListFilter implements FileNameFilter { 1114 1115 @Override accept(String fullPath)1116 public boolean accept(String fullPath) { 1117 if (fullPath.endsWith(".class")) { 1118 String path = fixPath(fullPath); 1119 return classesInMainDex.contains(path); 1120 } else { 1121 return true; 1122 } 1123 } 1124 } 1125 1126 /** 1127 * A best effort conservative filter for when file path can <b>not</b> be trusted. 1128 */ 1129 private static class BestEffortMainDexListFilter implements FileNameFilter { 1130 1131 Map<String, List<String>> map = new HashMap<String, List<String>>(); 1132 BestEffortMainDexListFilter()1133 public BestEffortMainDexListFilter() { 1134 for (String pathOfClass : classesInMainDex) { 1135 String normalized = fixPath(pathOfClass); 1136 String simple = getSimpleName(normalized); 1137 List<String> fullPath = map.get(simple); 1138 if (fullPath == null) { 1139 fullPath = new ArrayList<String>(1); 1140 map.put(simple, fullPath); 1141 } 1142 fullPath.add(normalized); 1143 } 1144 } 1145 1146 @Override accept(String path)1147 public boolean accept(String path) { 1148 if (path.endsWith(".class")) { 1149 String normalized = fixPath(path); 1150 String simple = getSimpleName(normalized); 1151 List<String> fullPaths = map.get(simple); 1152 if (fullPaths != null) { 1153 for (String fullPath : fullPaths) { 1154 if (normalized.endsWith(fullPath)) { 1155 return true; 1156 } 1157 } 1158 } 1159 return false; 1160 } else { 1161 return true; 1162 } 1163 } 1164 getSimpleName(String path)1165 private static String getSimpleName(String path) { 1166 int index = path.lastIndexOf('/'); 1167 if (index >= 0) { 1168 return path.substring(index + 1); 1169 } else { 1170 return path; 1171 } 1172 } 1173 } 1174 1175 /** 1176 * Exception class used to halt processing prematurely. 1177 */ 1178 private static class StopProcessing extends RuntimeException { 1179 // This space intentionally left blank. 1180 } 1181 1182 /** 1183 * Command-line argument parser and access. 1184 */ 1185 public static class Arguments { 1186 1187 private static final String MINIMAL_MAIN_DEX_OPTION = "--minimal-main-dex"; 1188 1189 private static final String MAIN_DEX_LIST_OPTION = "--main-dex-list"; 1190 1191 private static final String MULTI_DEX_OPTION = "--multi-dex"; 1192 1193 private static final String NUM_THREADS_OPTION = "--num-threads"; 1194 1195 private static final String INCREMENTAL_OPTION = "--incremental"; 1196 1197 private static final String INPUT_LIST_OPTION = "--input-list"; 1198 1199 /** whether to run in debug mode */ 1200 public boolean debug = false; 1201 1202 /** whether to emit high-level verbose human-oriented output */ 1203 public boolean verbose = false; 1204 1205 /** whether to emit verbose human-oriented output in the dump file */ 1206 public boolean verboseDump = false; 1207 1208 /** whether we are constructing a core library */ 1209 public boolean coreLibrary = false; 1210 1211 /** {@code null-ok;} particular method to dump */ 1212 public String methodToDump = null; 1213 1214 /** max width for columnar output */ 1215 public int dumpWidth = 0; 1216 1217 /** {@code null-ok;} output file name for binary file */ 1218 public String outName = null; 1219 1220 /** {@code null-ok;} output file name for human-oriented dump */ 1221 public String humanOutName = null; 1222 1223 /** whether strict file-name-vs-class-name checking should be done */ 1224 public boolean strictNameCheck = true; 1225 1226 /** 1227 * whether it is okay for there to be no {@code .class} files 1228 * to process 1229 */ 1230 public boolean emptyOk = false; 1231 1232 /** 1233 * whether the binary output is to be a {@code .jar} file 1234 * instead of a plain {@code .dex} 1235 */ 1236 public boolean jarOutput = false; 1237 1238 /** 1239 * when writing a {@code .jar} file, whether to still 1240 * keep the {@code .class} files 1241 */ 1242 public boolean keepClassesInJar = false; 1243 1244 /** how much source position info to preserve */ 1245 public int positionInfo = PositionList.LINES; 1246 1247 /** whether to keep local variable information */ 1248 public boolean localInfo = true; 1249 1250 /** whether to merge with the output dex file if it exists. */ 1251 public boolean incremental = false; 1252 1253 /** whether to force generation of const-string/jumbo for all indexes, 1254 * to allow merges between dex files with many strings. */ 1255 public boolean forceJumbo = false; 1256 1257 /** {@code non-null} after {@link #parse}; file name arguments */ 1258 public String[] fileNames; 1259 1260 /** whether to do SSA/register optimization */ 1261 public boolean optimize = true; 1262 1263 /** Filename containg list of methods to optimize */ 1264 public String optimizeListFile = null; 1265 1266 /** Filename containing list of methods to NOT optimize */ 1267 public String dontOptimizeListFile = null; 1268 1269 /** Whether to print statistics to stdout at end of compile cycle */ 1270 public boolean statistics; 1271 1272 /** Options for class file transformation */ 1273 public CfOptions cfOptions; 1274 1275 /** Options for dex file output */ 1276 public DexOptions dexOptions; 1277 1278 /** number of threads to run with */ 1279 public int numThreads = 1; 1280 1281 /** generation of multiple dex is allowed */ 1282 public boolean multiDex = false; 1283 1284 /** Optional file containing a list of class files containing classes to be forced in main 1285 * dex */ 1286 public String mainDexListFile = null; 1287 1288 /** Produce the smallest possible main dex. Ignored unless multiDex is true and 1289 * mainDexListFile is specified and non empty. */ 1290 public boolean minimalMainDex = false; 1291 1292 /** Optional list containing inputs read in from a file. */ 1293 private List<String> inputList = null; 1294 1295 private int maxNumberOfIdxPerDex = DexFormat.MAX_MEMBER_IDX + 1; 1296 1297 private static class ArgumentsParser { 1298 1299 /** The arguments to process. */ 1300 private final String[] arguments; 1301 /** The index of the next argument to process. */ 1302 private int index; 1303 /** The current argument being processed after a {@link #getNext()} call. */ 1304 private String current; 1305 /** The last value of an argument processed by {@link #isArg(String)}. */ 1306 private String lastValue; 1307 ArgumentsParser(String[] arguments)1308 public ArgumentsParser(String[] arguments) { 1309 this.arguments = arguments; 1310 index = 0; 1311 } 1312 getCurrent()1313 public String getCurrent() { 1314 return current; 1315 } 1316 getLastValue()1317 public String getLastValue() { 1318 return lastValue; 1319 } 1320 1321 /** 1322 * Moves on to the next argument. 1323 * Returns false when we ran out of arguments that start with --. 1324 */ getNext()1325 public boolean getNext() { 1326 if (index >= arguments.length) { 1327 return false; 1328 } 1329 current = arguments[index]; 1330 if (current.equals("--") || !current.startsWith("--")) { 1331 return false; 1332 } 1333 index++; 1334 return true; 1335 } 1336 1337 /** 1338 * Similar to {@link #getNext()}, this moves on the to next argument. 1339 * It does not check however whether the argument starts with -- 1340 * and thus can be used to retrieve values. 1341 */ getNextValue()1342 private boolean getNextValue() { 1343 if (index >= arguments.length) { 1344 return false; 1345 } 1346 current = arguments[index]; 1347 index++; 1348 return true; 1349 } 1350 1351 /** 1352 * Returns all the arguments that have not been processed yet. 1353 */ getRemaining()1354 public String[] getRemaining() { 1355 int n = arguments.length - index; 1356 String[] remaining = new String[n]; 1357 if (n > 0) { 1358 System.arraycopy(arguments, index, remaining, 0, n); 1359 } 1360 return remaining; 1361 } 1362 1363 /** 1364 * Checks the current argument against the given prefix. 1365 * If prefix is in the form '--name=', an extra value is expected. 1366 * The argument can then be in the form '--name=value' or as a 2-argument 1367 * form '--name value'. 1368 */ isArg(String prefix)1369 public boolean isArg(String prefix) { 1370 int n = prefix.length(); 1371 if (n > 0 && prefix.charAt(n-1) == '=') { 1372 // Argument accepts a value. Capture it. 1373 if (current.startsWith(prefix)) { 1374 // Argument is in the form --name=value, split the value out 1375 lastValue = current.substring(n); 1376 return true; 1377 } else { 1378 // Check whether we have "--name value" as 2 arguments 1379 prefix = prefix.substring(0, n-1); 1380 if (current.equals(prefix)) { 1381 if (getNextValue()) { 1382 lastValue = current; 1383 return true; 1384 } else { 1385 System.err.println("Missing value after parameter " + prefix); 1386 throw new UsageException(); 1387 } 1388 } 1389 return false; 1390 } 1391 } else { 1392 // Argument does not accept a value. 1393 return current.equals(prefix); 1394 } 1395 } 1396 } 1397 1398 /** 1399 * Parses the given command-line arguments. 1400 * 1401 * @param args {@code non-null;} the arguments 1402 */ parse(String[] args)1403 public void parse(String[] args) { 1404 ArgumentsParser parser = new ArgumentsParser(args); 1405 1406 boolean outputIsDirectory = false; 1407 boolean outputIsDirectDex = false; 1408 1409 while(parser.getNext()) { 1410 if (parser.isArg("--debug")) { 1411 debug = true; 1412 } else if (parser.isArg("--verbose")) { 1413 verbose = true; 1414 } else if (parser.isArg("--verbose-dump")) { 1415 verboseDump = true; 1416 } else if (parser.isArg("--no-files")) { 1417 emptyOk = true; 1418 } else if (parser.isArg("--no-optimize")) { 1419 optimize = false; 1420 } else if (parser.isArg("--no-strict")) { 1421 strictNameCheck = false; 1422 } else if (parser.isArg("--core-library")) { 1423 coreLibrary = true; 1424 } else if (parser.isArg("--statistics")) { 1425 statistics = true; 1426 } else if (parser.isArg("--optimize-list=")) { 1427 if (dontOptimizeListFile != null) { 1428 System.err.println("--optimize-list and " 1429 + "--no-optimize-list are incompatible."); 1430 throw new UsageException(); 1431 } 1432 optimize = true; 1433 optimizeListFile = parser.getLastValue(); 1434 } else if (parser.isArg("--no-optimize-list=")) { 1435 if (dontOptimizeListFile != null) { 1436 System.err.println("--optimize-list and " 1437 + "--no-optimize-list are incompatible."); 1438 throw new UsageException(); 1439 } 1440 optimize = true; 1441 dontOptimizeListFile = parser.getLastValue(); 1442 } else if (parser.isArg("--keep-classes")) { 1443 keepClassesInJar = true; 1444 } else if (parser.isArg("--output=")) { 1445 outName = parser.getLastValue(); 1446 if (new File(outName).isDirectory()) { 1447 jarOutput = false; 1448 outputIsDirectory = true; 1449 } else if (FileUtils.hasArchiveSuffix(outName)) { 1450 jarOutput = true; 1451 } else if (outName.endsWith(".dex") || 1452 outName.equals("-")) { 1453 jarOutput = false; 1454 outputIsDirectDex = true; 1455 } else { 1456 System.err.println("unknown output extension: " + 1457 outName); 1458 throw new UsageException(); 1459 } 1460 } else if (parser.isArg("--dump-to=")) { 1461 humanOutName = parser.getLastValue(); 1462 } else if (parser.isArg("--dump-width=")) { 1463 dumpWidth = Integer.parseInt(parser.getLastValue()); 1464 } else if (parser.isArg("--dump-method=")) { 1465 methodToDump = parser.getLastValue(); 1466 jarOutput = false; 1467 } else if (parser.isArg("--positions=")) { 1468 String pstr = parser.getLastValue().intern(); 1469 if (pstr == "none") { 1470 positionInfo = PositionList.NONE; 1471 } else if (pstr == "important") { 1472 positionInfo = PositionList.IMPORTANT; 1473 } else if (pstr == "lines") { 1474 positionInfo = PositionList.LINES; 1475 } else { 1476 System.err.println("unknown positions option: " + 1477 pstr); 1478 throw new UsageException(); 1479 } 1480 } else if (parser.isArg("--no-locals")) { 1481 localInfo = false; 1482 } else if (parser.isArg(NUM_THREADS_OPTION + "=")) { 1483 numThreads = Integer.parseInt(parser.getLastValue()); 1484 } else if (parser.isArg(INCREMENTAL_OPTION)) { 1485 incremental = true; 1486 } else if (parser.isArg("--force-jumbo")) { 1487 forceJumbo = true; 1488 } else if (parser.isArg(MULTI_DEX_OPTION)) { 1489 multiDex = true; 1490 } else if (parser.isArg(MAIN_DEX_LIST_OPTION + "=")) { 1491 mainDexListFile = parser.getLastValue(); 1492 } else if (parser.isArg(MINIMAL_MAIN_DEX_OPTION)) { 1493 minimalMainDex = true; 1494 } else if (parser.isArg("--set-max-idx-number=")) { // undocumented test option 1495 maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue()); 1496 } else if(parser.isArg(INPUT_LIST_OPTION + "=")) { 1497 File inputListFile = new File(parser.getLastValue()); 1498 try{ 1499 inputList = new ArrayList<String>(); 1500 readPathsFromFile(inputListFile.getAbsolutePath(), inputList); 1501 } catch(IOException e) { 1502 System.err.println( 1503 "Unable to read input list file: " + inputListFile.getName()); 1504 // problem reading the file so we should halt execution 1505 throw new UsageException(); 1506 } 1507 } else { 1508 System.err.println("unknown option: " + parser.getCurrent()); 1509 throw new UsageException(); 1510 } 1511 } 1512 1513 fileNames = parser.getRemaining(); 1514 if(inputList != null && !inputList.isEmpty()) { 1515 // append the file names to the end of the input list 1516 inputList.addAll(Arrays.asList(fileNames)); 1517 fileNames = inputList.toArray(new String[inputList.size()]); 1518 } 1519 1520 if (fileNames.length == 0) { 1521 if (!emptyOk) { 1522 System.err.println("no input files specified"); 1523 throw new UsageException(); 1524 } 1525 } else if (emptyOk) { 1526 System.out.println("ignoring input files"); 1527 } 1528 1529 if ((humanOutName == null) && (methodToDump != null)) { 1530 humanOutName = "-"; 1531 } 1532 1533 if (mainDexListFile != null && !multiDex) { 1534 System.err.println(MAIN_DEX_LIST_OPTION + " is only supported in combination with " 1535 + MULTI_DEX_OPTION); 1536 throw new UsageException(); 1537 } 1538 1539 if (minimalMainDex && (mainDexListFile == null || !multiDex)) { 1540 System.err.println(MINIMAL_MAIN_DEX_OPTION + " is only supported in combination with " 1541 + MULTI_DEX_OPTION + " and " + MAIN_DEX_LIST_OPTION); 1542 throw new UsageException(); 1543 } 1544 1545 if (multiDex && numThreads != 1) { 1546 System.out.println(NUM_THREADS_OPTION + " is ignored when used with " 1547 + MULTI_DEX_OPTION); 1548 numThreads = 1; 1549 } 1550 1551 if (multiDex && incremental) { 1552 System.err.println(INCREMENTAL_OPTION + " is not supported with " 1553 + MULTI_DEX_OPTION); 1554 throw new UsageException(); 1555 } 1556 1557 if (multiDex && outputIsDirectDex) { 1558 System.err.println("Unsupported output \"" + outName +"\". " + MULTI_DEX_OPTION + 1559 " supports only archive or directory output"); 1560 throw new UsageException(); 1561 } 1562 1563 if (outputIsDirectory && !multiDex) { 1564 outName = new File(outName, DexFormat.DEX_IN_JAR_NAME).getPath(); 1565 } 1566 1567 makeOptionsObjects(); 1568 } 1569 1570 /** 1571 * Copies relevent arguments over into CfOptions and 1572 * DexOptions instances. 1573 */ makeOptionsObjects()1574 private void makeOptionsObjects() { 1575 cfOptions = new CfOptions(); 1576 cfOptions.positionInfo = positionInfo; 1577 cfOptions.localInfo = localInfo; 1578 cfOptions.strictNameCheck = strictNameCheck; 1579 cfOptions.optimize = optimize; 1580 cfOptions.optimizeListFile = optimizeListFile; 1581 cfOptions.dontOptimizeListFile = dontOptimizeListFile; 1582 cfOptions.statistics = statistics; 1583 cfOptions.warn = DxConsole.err; 1584 1585 dexOptions = new DexOptions(); 1586 dexOptions.forceJumbo = forceJumbo; 1587 } 1588 } 1589 1590 /** Callable helper class to process files in multiple threads */ 1591 private static class ParallelProcessor implements Callable<Void> { 1592 1593 ClassPathOpener classPathOpener; 1594 ParallelProcessor(ClassPathOpener classPathOpener)1595 private ParallelProcessor(ClassPathOpener classPathOpener) { 1596 this.classPathOpener = classPathOpener; 1597 } 1598 1599 @Override call()1600 public Void call() throws Exception { 1601 if (classPathOpener.process()) { 1602 anyFilesProcessed = true; 1603 } 1604 return null; 1605 } 1606 } 1607 } 1608