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.UsageException; 31 import com.android.dx.dex.DexOptions; 32 import com.android.dx.dex.cf.CfOptions; 33 import com.android.dx.dex.cf.CfTranslator; 34 import com.android.dx.dex.code.PositionList; 35 import com.android.dx.dex.file.ClassDefItem; 36 import com.android.dx.dex.file.DexFile; 37 import com.android.dx.dex.file.EncodedMethod; 38 import com.android.dx.merge.CollisionPolicy; 39 import com.android.dx.merge.DexMerger; 40 import com.android.dx.rop.annotation.Annotation; 41 import com.android.dx.rop.annotation.Annotations; 42 import com.android.dx.rop.annotation.AnnotationsList; 43 import com.android.dx.rop.code.RegisterSpec; 44 import com.android.dx.rop.cst.CstNat; 45 import com.android.dx.rop.cst.CstString; 46 import com.android.dx.rop.cst.CstType; 47 import com.android.dx.rop.type.Prototype; 48 import com.android.dx.rop.type.Type; 49 import java.io.BufferedReader; 50 import java.io.ByteArrayInputStream; 51 import java.io.ByteArrayOutputStream; 52 import java.io.File; 53 import java.io.FileOutputStream; 54 import java.io.FileReader; 55 import java.io.IOException; 56 import java.io.OutputStream; 57 import java.io.OutputStreamWriter; 58 import java.io.PrintWriter; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.Collection; 62 import java.util.HashMap; 63 import java.util.HashSet; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Set; 67 import java.util.TreeMap; 68 import java.util.concurrent.ArrayBlockingQueue; 69 import java.util.concurrent.Callable; 70 import java.util.concurrent.ExecutionException; 71 import java.util.concurrent.ExecutorService; 72 import java.util.concurrent.Executors; 73 import java.util.concurrent.Future; 74 import java.util.concurrent.ThreadPoolExecutor; 75 import java.util.concurrent.TimeUnit; 76 import java.util.concurrent.atomic.AtomicInteger; 77 import java.util.jar.Attributes; 78 import java.util.jar.JarEntry; 79 import java.util.jar.JarOutputStream; 80 import java.util.jar.Manifest; 81 82 /** 83 * Main class for the class file translator. 84 */ 85 public class Main { 86 87 /** 88 * File extension of a {@code .dex} file. 89 */ 90 private static final String DEX_EXTENSION = ".dex"; 91 92 /** 93 * File name prefix of a {@code .dex} file automatically loaded in an 94 * archive. 95 */ 96 private static final String DEX_PREFIX = "classes"; 97 98 /** 99 * {@code non-null;} the lengthy message that tries to discourage 100 * people from defining core classes in applications 101 */ 102 private static final String IN_RE_CORE_CLASSES = 103 "Ill-advised or mistaken usage of a core class (java.* or javax.*)\n" + 104 "when not building a core library.\n\n" + 105 "This is often due to inadvertently including a core library file\n" + 106 "in your application's project, when using an IDE (such as\n" + 107 "Eclipse). If you are sure you're not intentionally defining a\n" + 108 "core class, then this is the most likely explanation of what's\n" + 109 "going on.\n\n" + 110 "However, you might actually be trying to define a class in a core\n" + 111 "namespace, the source of which you may have taken, for example,\n" + 112 "from a non-Android virtual machine project. This will most\n" + 113 "assuredly not work. At a minimum, it jeopardizes the\n" + 114 "compatibility of your app with future versions of the platform.\n" + 115 "It is also often of questionable legality.\n\n" + 116 "If you really intend to build a core library -- which is only\n" + 117 "appropriate as part of creating a full virtual machine\n" + 118 "distribution, as opposed to compiling an application -- then use\n" + 119 "the \"--core-library\" option to suppress this error message.\n\n" + 120 "If you go ahead and use \"--core-library\" but are in fact\n" + 121 "building an application, then be forewarned that your application\n" + 122 "will still fail to build or run, at some point. Please be\n" + 123 "prepared for angry customers who find, for example, that your\n" + 124 "application ceases to function once they upgrade their operating\n" + 125 "system. You will be to blame for this problem.\n\n" + 126 "If you are legitimately using some code that happens to be in a\n" + 127 "core package, then the easiest safe alternative you have is to\n" + 128 "repackage that code. That is, move the classes in question into\n" + 129 "your own package namespace. This means that they will never be in\n" + 130 "conflict with core system classes. JarJar is a tool that may help\n" + 131 "you in this endeavor. If you find that you cannot do this, then\n" + 132 "that is an indication that the path you are on will ultimately\n" + 133 "lead to pain, suffering, grief, and lamentation.\n"; 134 135 /** 136 * {@code non-null;} name of the standard manifest file in {@code .jar} 137 * files 138 */ 139 private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 140 141 /** 142 * {@code non-null;} attribute name for the (quasi-standard?) 143 * {@code Created-By} attribute 144 */ 145 private static final Attributes.Name CREATED_BY = 146 new Attributes.Name("Created-By"); 147 148 /** 149 * {@code non-null;} list of {@code javax} subpackages that are considered 150 * to be "core". <b>Note:</b>: This list must be sorted, since it 151 * is binary-searched. 152 */ 153 private static final String[] JAVAX_CORE = { 154 "accessibility", "crypto", "imageio", "management", "naming", "net", 155 "print", "rmi", "security", "sip", "sound", "sql", "swing", 156 "transaction", "xml" 157 }; 158 159 /* Array.newInstance may be added by RopperMachine, 160 * ArrayIndexOutOfBoundsException.<init> may be added by EscapeAnalysis */ 161 private static final int MAX_METHOD_ADDED_DURING_DEX_CREATION = 2; 162 163 /* <primitive types box class>.TYPE */ 164 private static final int MAX_FIELD_ADDED_DURING_DEX_CREATION = 9; 165 166 /** number of errors during processing */ 167 private AtomicInteger errors = new AtomicInteger(0); 168 169 /** {@code non-null;} parsed command-line arguments */ 170 private Arguments args; 171 172 /** {@code non-null;} output file in-progress */ 173 private DexFile outputDex; 174 175 /** 176 * {@code null-ok;} map of resources to include in the output, or 177 * {@code null} if resources are being ignored 178 */ 179 private TreeMap<String, byte[]> outputResources; 180 181 /** Library .dex files to merge into the output .dex. */ 182 private final List<byte[]> libraryDexBuffers = new ArrayList<byte[]>(); 183 184 /** Thread pool object used for multi-thread class translation. */ 185 private ExecutorService classTranslatorPool; 186 187 /** Single thread executor, for collecting results of parallel translation, 188 * and adding classes to dex file in original input file order. */ 189 private ExecutorService classDefItemConsumer; 190 191 /** Futures for {@code classDefItemConsumer} tasks. */ 192 private List<Future<Boolean>> addToDexFutures = 193 new ArrayList<Future<Boolean>>(); 194 195 /** Thread pool object used for multi-thread dex conversion (to byte array). 196 * Used in combination with multi-dex support, to allow outputing 197 * a completed dex file, in parallel with continuing processing. */ 198 private ExecutorService dexOutPool; 199 200 /** Futures for {@code dexOutPool} task. */ 201 private List<Future<byte[]>> dexOutputFutures = new ArrayList<Future<byte[]>>(); 202 203 /** Lock object used to to coordinate dex file rotation, and 204 * multi-threaded translation. */ 205 private Object dexRotationLock = new Object(); 206 207 /** Record the number if method indices "reserved" for files 208 * committed to translation in the context of the current dex 209 * file, but not yet added. */ 210 private int maxMethodIdsInProcess = 0; 211 212 /** Record the number if field indices "reserved" for files 213 * committed to translation in the context of the current dex 214 * file, but not yet added. */ 215 private int maxFieldIdsInProcess = 0; 216 217 /** true if any files are successfully processed */ 218 private volatile boolean anyFilesProcessed; 219 220 /** class files older than this must be defined in the target dex file. */ 221 private long minimumFileAge = 0; 222 223 private Set<String> classesInMainDex = null; 224 225 private List<byte[]> dexOutputArrays = new ArrayList<byte[]>(); 226 227 private OutputStreamWriter humanOutWriter = null; 228 229 private final DxContext context; 230 Main(DxContext context)231 public Main(DxContext context) { 232 this.context = context; 233 } 234 235 /** 236 * Run and exit if something unexpected happened. 237 * @param argArray the command line arguments 238 */ main(String[] argArray)239 public static void main(String[] argArray) throws IOException { 240 DxContext context = new DxContext(); 241 Arguments arguments = new Arguments(context); 242 arguments.parse(argArray); 243 244 int result = new Main(context).runDx(arguments); 245 246 if (result != 0) { 247 System.exit(result); 248 } 249 } 250 clearInternTables()251 public static void clearInternTables() { 252 Prototype.clearInternTable(); 253 RegisterSpec.clearInternTable(); 254 CstType.clearInternTable(); 255 Type.clearInternTable(); 256 } 257 258 /** 259 * Run and return a result code. 260 * @param arguments the data + parameters for the conversion 261 * @return 0 if success > 0 otherwise. 262 */ run(Arguments arguments)263 public static int run(Arguments arguments) throws IOException { 264 return new Main(new DxContext()).runDx(arguments); 265 } 266 runDx(Arguments arguments)267 public int runDx(Arguments arguments) throws IOException { 268 269 // Reset the error count to start fresh. 270 errors.set(0); 271 // empty the list, so that tools that load dx and keep it around 272 // for multiple runs don't reuse older buffers. 273 libraryDexBuffers.clear(); 274 275 args = arguments; 276 args.makeOptionsObjects(); 277 278 OutputStream humanOutRaw = null; 279 if (args.humanOutName != null) { 280 humanOutRaw = openOutput(args.humanOutName); 281 humanOutWriter = new OutputStreamWriter(humanOutRaw); 282 } 283 284 try { 285 if (args.multiDex) { 286 return runMultiDex(); 287 } else { 288 return runMonoDex(); 289 } 290 } finally { 291 closeOutput(humanOutRaw); 292 } 293 } 294 runMonoDex()295 private int runMonoDex() throws IOException { 296 297 File incrementalOutFile = null; 298 if (args.incremental) { 299 if (args.outName == null) { 300 context.err.println( 301 "error: no incremental output name specified"); 302 return -1; 303 } 304 incrementalOutFile = new File(args.outName); 305 if (incrementalOutFile.exists()) { 306 minimumFileAge = incrementalOutFile.lastModified(); 307 } 308 } 309 310 if (!processAllFiles()) { 311 return 1; 312 } 313 314 if (args.incremental && !anyFilesProcessed) { 315 return 0; // this was a no-op incremental build 316 } 317 318 // this array is null if no classes were defined 319 byte[] outArray = null; 320 321 if (!outputDex.isEmpty() || (args.humanOutName != null)) { 322 outArray = writeDex(outputDex); 323 324 if (outArray == null) { 325 return 2; 326 } 327 } 328 329 if (args.incremental) { 330 outArray = mergeIncremental(outArray, incrementalOutFile); 331 } 332 333 outArray = mergeLibraryDexBuffers(outArray); 334 335 if (args.jarOutput) { 336 // Effectively free up the (often massive) DexFile memory. 337 outputDex = null; 338 339 if (outArray != null) { 340 outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray); 341 } 342 if (!createJar(args.outName)) { 343 return 3; 344 } 345 } else if (outArray != null && args.outName != null) { 346 OutputStream out = openOutput(args.outName); 347 out.write(outArray); 348 closeOutput(out); 349 } 350 351 return 0; 352 } 353 runMultiDex()354 private int runMultiDex() throws IOException { 355 356 assert !args.incremental; 357 358 if (args.mainDexListFile != null) { 359 classesInMainDex = new HashSet<String>(); 360 readPathsFromFile(args.mainDexListFile, classesInMainDex); 361 } 362 363 dexOutPool = Executors.newFixedThreadPool(args.numThreads); 364 365 if (!processAllFiles()) { 366 return 1; 367 } 368 369 if (!libraryDexBuffers.isEmpty()) { 370 throw new DexException("Library dex files are not supported in multi-dex mode"); 371 } 372 373 if (outputDex != null) { 374 // this array is null if no classes were defined 375 376 dexOutputFutures.add(dexOutPool.submit(new DexWriter(outputDex))); 377 378 // Effectively free up the (often massive) DexFile memory. 379 outputDex = null; 380 } 381 try { 382 dexOutPool.shutdown(); 383 if (!dexOutPool.awaitTermination(600L, TimeUnit.SECONDS)) { 384 throw new RuntimeException("Timed out waiting for dex writer threads."); 385 } 386 387 for (Future<byte[]> f : dexOutputFutures) { 388 dexOutputArrays.add(f.get()); 389 } 390 391 } catch (InterruptedException ex) { 392 dexOutPool.shutdownNow(); 393 throw new RuntimeException("A dex writer thread has been interrupted."); 394 } catch (Exception e) { 395 dexOutPool.shutdownNow(); 396 throw new RuntimeException("Unexpected exception in dex writer thread"); 397 } 398 399 if (args.jarOutput) { 400 for (int i = 0; i < dexOutputArrays.size(); i++) { 401 outputResources.put(getDexFileName(i), 402 dexOutputArrays.get(i)); 403 } 404 405 if (!createJar(args.outName)) { 406 return 3; 407 } 408 } else if (args.outName != null) { 409 File outDir = new File(args.outName); 410 assert outDir.isDirectory(); 411 for (int i = 0; i < dexOutputArrays.size(); i++) { 412 OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i))); 413 try { 414 out.write(dexOutputArrays.get(i)); 415 } finally { 416 closeOutput(out); 417 } 418 } 419 } 420 421 return 0; 422 } 423 getDexFileName(int i)424 private static String getDexFileName(int i) { 425 if (i == 0) { 426 return DexFormat.DEX_IN_JAR_NAME; 427 } else { 428 return DEX_PREFIX + (i + 1) + DEX_EXTENSION; 429 } 430 } 431 readPathsFromFile(String fileName, Collection<String> paths)432 private static void readPathsFromFile(String fileName, Collection<String> paths) throws IOException { 433 BufferedReader bfr = null; 434 try { 435 FileReader fr = new FileReader(fileName); 436 bfr = new BufferedReader(fr); 437 438 String line; 439 440 while (null != (line = bfr.readLine())) { 441 paths.add(fixPath(line)); 442 } 443 444 } finally { 445 if (bfr != null) { 446 bfr.close(); 447 } 448 } 449 } 450 451 /** 452 * Merges the dex files {@code update} and {@code base}, preferring 453 * {@code update}'s definition for types defined in both dex files. 454 * 455 * @param base a file to find the previous dex file. May be a .dex file, a 456 * jar file possibly containing a .dex file, or null. 457 * @return the bytes of the merged dex file, or null if both the update 458 * and the base dex do not exist. 459 */ mergeIncremental(byte[] update, File base)460 private byte[] mergeIncremental(byte[] update, File base) throws IOException { 461 Dex dexA = null; 462 Dex dexB = null; 463 464 if (update != null) { 465 dexA = new Dex(update); 466 } 467 468 if (base.exists()) { 469 dexB = new Dex(base); 470 } 471 472 Dex result; 473 if (dexA == null && dexB == null) { 474 return null; 475 } else if (dexA == null) { 476 result = dexB; 477 } else if (dexB == null) { 478 result = dexA; 479 } else { 480 result = new DexMerger(new Dex[] {dexA, dexB}, CollisionPolicy.KEEP_FIRST, context).merge(); 481 } 482 483 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 484 result.writeTo(bytesOut); 485 return bytesOut.toByteArray(); 486 } 487 488 /** 489 * Merges the dex files in library jars. If multiple dex files define the 490 * same type, this fails with an exception. 491 */ mergeLibraryDexBuffers(byte[] outArray)492 private byte[] mergeLibraryDexBuffers(byte[] outArray) throws IOException { 493 ArrayList<Dex> dexes = new ArrayList<Dex>(); 494 if (outArray != null) { 495 dexes.add(new Dex(outArray)); 496 } 497 for (byte[] libraryDex : libraryDexBuffers) { 498 dexes.add(new Dex(libraryDex)); 499 } 500 if (dexes.isEmpty()) { 501 return null; 502 } 503 Dex merged = new DexMerger(dexes.toArray(new Dex[dexes.size()]), CollisionPolicy.FAIL, context).merge(); 504 return merged.getBytes(); 505 } 506 507 /** 508 * Constructs the output {@link DexFile}, fill it in with all the 509 * specified classes, and populate the resources map if required. 510 * 511 * @return whether processing was successful 512 */ processAllFiles()513 private boolean processAllFiles() { 514 createDexFile(); 515 516 if (args.jarOutput) { 517 outputResources = new TreeMap<String, byte[]>(); 518 } 519 520 anyFilesProcessed = false; 521 String[] fileNames = args.fileNames; 522 Arrays.sort(fileNames); 523 524 // translate classes in parallel 525 classTranslatorPool = new ThreadPoolExecutor(args.numThreads, 526 args.numThreads, 0, TimeUnit.SECONDS, 527 new ArrayBlockingQueue<Runnable>(2 * args.numThreads, true), 528 new ThreadPoolExecutor.CallerRunsPolicy()); 529 // collect translated and write to dex in order 530 classDefItemConsumer = Executors.newSingleThreadExecutor(); 531 532 533 try { 534 if (args.mainDexListFile != null) { 535 // with --main-dex-list 536 FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() : 537 new BestEffortMainDexListFilter(); 538 539 // forced in main dex 540 for (int i = 0; i < fileNames.length; i++) { 541 processOne(fileNames[i], mainPassFilter); 542 } 543 544 if (dexOutputFutures.size() > 0) { 545 throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION 546 + ", main dex capacity exceeded"); 547 } 548 549 if (args.minimalMainDex) { 550 // start second pass directly in a secondary dex file. 551 552 // Wait for classes in progress to complete 553 synchronized(dexRotationLock) { 554 while(maxMethodIdsInProcess > 0 || maxFieldIdsInProcess > 0) { 555 try { 556 dexRotationLock.wait(); 557 } catch(InterruptedException ex) { 558 /* ignore */ 559 } 560 } 561 } 562 563 rotateDexFile(); 564 } 565 566 // remaining files 567 FileNameFilter filter = new RemoveModuleInfoFilter(new NotFilter(mainPassFilter)); 568 for (int i = 0; i < fileNames.length; i++) { 569 processOne(fileNames[i], filter); 570 } 571 } else { 572 // without --main-dex-list 573 FileNameFilter filter = new RemoveModuleInfoFilter(ClassPathOpener.acceptAll); 574 for (int i = 0; i < fileNames.length; i++) { 575 processOne(fileNames[i], filter); 576 } 577 } 578 } catch (StopProcessing ex) { 579 /* 580 * Ignore it and just let the error reporting do 581 * their things. 582 */ 583 } 584 585 try { 586 classTranslatorPool.shutdown(); 587 classTranslatorPool.awaitTermination(600L, TimeUnit.SECONDS); 588 classDefItemConsumer.shutdown(); 589 classDefItemConsumer.awaitTermination(600L, TimeUnit.SECONDS); 590 591 for (Future<Boolean> f : addToDexFutures) { 592 try { 593 f.get(); 594 } catch(ExecutionException ex) { 595 // Catch any previously uncaught exceptions from 596 // class translation and adding to dex. 597 int count = errors.incrementAndGet(); 598 if (count < 10) { 599 if (args.debug) { 600 context.err.println("Uncaught translation error:"); 601 ex.getCause().printStackTrace(context.err); 602 } else { 603 context.err.println("Uncaught translation error: " + ex.getCause()); 604 } 605 } else { 606 throw new InterruptedException("Too many errors"); 607 } 608 } 609 } 610 611 } catch (InterruptedException ie) { 612 classTranslatorPool.shutdownNow(); 613 classDefItemConsumer.shutdownNow(); 614 throw new RuntimeException("Translation has been interrupted", ie); 615 } catch (Exception e) { 616 classTranslatorPool.shutdownNow(); 617 classDefItemConsumer.shutdownNow(); 618 e.printStackTrace(context.out); 619 throw new RuntimeException("Unexpected exception in translator thread.", e); 620 } 621 622 int errorNum = errors.get(); 623 if (errorNum != 0) { 624 context.err.println(errorNum + " error" + 625 ((errorNum == 1) ? "" : "s") + "; aborting"); 626 return false; 627 } 628 629 if (args.incremental && !anyFilesProcessed) { 630 return true; 631 } 632 633 if (!(anyFilesProcessed || args.emptyOk)) { 634 context.err.println("no classfiles specified"); 635 return false; 636 } 637 638 if (args.optimize && args.statistics) { 639 context.codeStatistics.dumpStatistics(context.out); 640 } 641 642 return true; 643 } 644 createDexFile()645 private void createDexFile() { 646 outputDex = new DexFile(args.dexOptions); 647 648 if (args.dumpWidth != 0) { 649 outputDex.setDumpWidth(args.dumpWidth); 650 } 651 } 652 rotateDexFile()653 private void rotateDexFile() { 654 if (outputDex != null) { 655 if (dexOutPool != null) { 656 dexOutputFutures.add(dexOutPool.submit(new DexWriter(outputDex))); 657 } else { 658 dexOutputArrays.add(writeDex(outputDex)); 659 } 660 } 661 662 createDexFile(); 663 } 664 665 /** 666 * Processes one pathname element. 667 * 668 * @param pathname {@code non-null;} the pathname to process. May 669 * be the path of a class file, a jar file, or a directory 670 * containing class files. 671 * @param filter {@code non-null;} A filter for excluding files. 672 */ processOne(String pathname, FileNameFilter filter)673 private void processOne(String pathname, FileNameFilter filter) { 674 ClassPathOpener opener; 675 676 opener = new ClassPathOpener(pathname, true, filter, new FileBytesConsumer()); 677 678 if (opener.process()) { 679 updateStatus(true); 680 } 681 } 682 updateStatus(boolean res)683 private void updateStatus(boolean res) { 684 anyFilesProcessed |= res; 685 } 686 687 688 /** 689 * Processes one file, which may be either a class or a resource. 690 * 691 * @param name {@code non-null;} name of the file 692 * @param bytes {@code non-null;} contents of the file 693 * @return whether processing was successful 694 */ processFileBytes(String name, long lastModified, byte[] bytes)695 private boolean processFileBytes(String name, long lastModified, byte[] bytes) { 696 697 boolean isClass = name.endsWith(".class"); 698 boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME); 699 boolean keepResources = (outputResources != null); 700 701 if (!isClass && !isClassesDex && !keepResources) { 702 if (args.verbose) { 703 context.out.println("ignored resource " + name); 704 } 705 return false; 706 } 707 708 if (args.verbose) { 709 context.out.println("processing " + name + "..."); 710 } 711 712 String fixedName = fixPath(name); 713 714 if (isClass) { 715 716 if (keepResources && args.keepClassesInJar) { 717 synchronized (outputResources) { 718 outputResources.put(fixedName, bytes); 719 } 720 } 721 if (lastModified < minimumFileAge) { 722 return true; 723 } 724 processClass(fixedName, bytes); 725 // Assume that an exception may occur. Status will be updated 726 // asynchronously, if the class compiles without error. 727 return false; 728 } else if (isClassesDex) { 729 synchronized (libraryDexBuffers) { 730 libraryDexBuffers.add(bytes); 731 } 732 return true; 733 } else { 734 synchronized (outputResources) { 735 outputResources.put(fixedName, bytes); 736 } 737 return true; 738 } 739 } 740 741 /** 742 * Processes one classfile. 743 * 744 * @param name {@code non-null;} name of the file, clipped such that it 745 * <i>should</i> correspond to the name of the class it contains 746 * @param bytes {@code non-null;} contents of the file 747 * @return whether processing was successful 748 */ processClass(String name, byte[] bytes)749 private boolean processClass(String name, byte[] bytes) { 750 if (! args.coreLibrary) { 751 checkClassName(name); 752 } 753 754 try { 755 new DirectClassFileConsumer(name, bytes, null).call( 756 new ClassParserTask(name, bytes).call()); 757 } catch (ParseException ex) { 758 // handled in FileBytesConsumer 759 throw ex; 760 } catch(Exception ex) { 761 throw new RuntimeException("Exception parsing classes", ex); 762 } 763 764 return true; 765 } 766 767 parseClass(String name, byte[] bytes)768 private DirectClassFile parseClass(String name, byte[] bytes) { 769 770 DirectClassFile cf = new DirectClassFile(bytes, name, 771 args.cfOptions.strictNameCheck); 772 cf.setAttributeFactory(StdAttributeFactory.THE_ONE); 773 cf.getMagic(); // triggers the actual parsing 774 return cf; 775 } 776 translateClass(byte[] bytes, DirectClassFile cf)777 private ClassDefItem translateClass(byte[] bytes, DirectClassFile cf) { 778 try { 779 return CfTranslator.translate(context, cf, bytes, args.cfOptions, 780 args.dexOptions, outputDex); 781 } catch (ParseException ex) { 782 context.err.println("\ntrouble processing:"); 783 if (args.debug) { 784 ex.printStackTrace(context.err); 785 } else { 786 ex.printContext(context.err); 787 } 788 } 789 errors.incrementAndGet(); 790 return null; 791 } 792 addClassToDex(ClassDefItem clazz)793 private boolean addClassToDex(ClassDefItem clazz) { 794 synchronized (outputDex) { 795 outputDex.add(clazz); 796 } 797 return true; 798 } 799 800 /** 801 * Check the class name to make sure it's not a "core library" 802 * class. If there is a problem, this updates the error count and 803 * throws an exception to stop processing. 804 * 805 * @param name {@code non-null;} the fully-qualified internal-form 806 * class name 807 */ checkClassName(String name)808 private void checkClassName(String name) { 809 boolean bogus = false; 810 811 if (name.startsWith("java/")) { 812 bogus = true; 813 } else if (name.startsWith("javax/")) { 814 int slashAt = name.indexOf('/', 6); 815 if (slashAt == -1) { 816 // Top-level javax classes are verboten. 817 bogus = true; 818 } else { 819 String pkg = name.substring(6, slashAt); 820 bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0); 821 } 822 } 823 824 if (! bogus) { 825 return; 826 } 827 828 /* 829 * The user is probably trying to include an entire desktop 830 * core library in a misguided attempt to get their application 831 * working. Try to help them understand what's happening. 832 */ 833 834 context.err.println("\ntrouble processing \"" + name + "\":\n\n" + 835 IN_RE_CORE_CLASSES); 836 errors.incrementAndGet(); 837 throw new StopProcessing(); 838 } 839 840 /** 841 * Converts {@link #outputDex} into a {@code byte[]} and do whatever 842 * human-oriented dumping is required. 843 * 844 * @return {@code null-ok;} the converted {@code byte[]} or {@code null} 845 * if there was a problem 846 */ writeDex(DexFile outputDex)847 private byte[] writeDex(DexFile outputDex) { 848 byte[] outArray = null; 849 850 try { 851 try { 852 if (args.methodToDump != null) { 853 /* 854 * Simply dump the requested method. Note: The call 855 * to toDex() is required just to get the underlying 856 * structures ready. 857 */ 858 outputDex.toDex(null, false); 859 dumpMethod(outputDex, args.methodToDump, humanOutWriter); 860 } else { 861 /* 862 * This is the usual case: Create an output .dex file, 863 * and write it, dump it, etc. 864 */ 865 outArray = outputDex.toDex(humanOutWriter, args.verboseDump); 866 } 867 868 if (args.statistics) { 869 context.out.println(outputDex.getStatistics().toHuman()); 870 } 871 } finally { 872 if (humanOutWriter != null) { 873 humanOutWriter.flush(); 874 } 875 } 876 } catch (Exception ex) { 877 if (args.debug) { 878 context.err.println("\ntrouble writing output:"); 879 ex.printStackTrace(context.err); 880 } else { 881 context.err.println("\ntrouble writing output: " + 882 ex.getMessage()); 883 } 884 return null; 885 } 886 return outArray; 887 } 888 889 /** 890 * Creates a jar file from the resources (including dex file arrays). 891 * 892 * @param fileName {@code non-null;} name of the file 893 * @return whether the creation was successful 894 */ createJar(String fileName)895 private boolean createJar(String fileName) { 896 /* 897 * Make or modify the manifest (as appropriate), put the dex 898 * array into the resources map, and then process the entire 899 * resources map in a uniform manner. 900 */ 901 902 try { 903 Manifest manifest = makeManifest(); 904 OutputStream out = openOutput(fileName); 905 JarOutputStream jarOut = new JarOutputStream(out, manifest); 906 907 try { 908 for (Map.Entry<String, byte[]> e : 909 outputResources.entrySet()) { 910 String name = e.getKey(); 911 byte[] contents = e.getValue(); 912 JarEntry entry = new JarEntry(name); 913 int length = contents.length; 914 915 if (args.verbose) { 916 context.out.println("writing " + name + "; size " + length + "..."); 917 } 918 919 entry.setSize(length); 920 jarOut.putNextEntry(entry); 921 jarOut.write(contents); 922 jarOut.closeEntry(); 923 } 924 } finally { 925 jarOut.finish(); 926 jarOut.flush(); 927 closeOutput(out); 928 } 929 } catch (Exception ex) { 930 if (args.debug) { 931 context.err.println("\ntrouble writing output:"); 932 ex.printStackTrace(context.err); 933 } else { 934 context.err.println("\ntrouble writing output: " + 935 ex.getMessage()); 936 } 937 return false; 938 } 939 940 return true; 941 } 942 943 /** 944 * Creates and returns the manifest to use for the output. This may 945 * modify {@link #outputResources} (removing the pre-existing manifest). 946 * 947 * @return {@code non-null;} the manifest 948 */ makeManifest()949 private Manifest makeManifest() throws IOException { 950 byte[] manifestBytes = outputResources.get(MANIFEST_NAME); 951 Manifest manifest; 952 Attributes attribs; 953 954 if (manifestBytes == null) { 955 // We need to construct an entirely new manifest. 956 manifest = new Manifest(); 957 attribs = manifest.getMainAttributes(); 958 attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 959 } else { 960 manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); 961 attribs = manifest.getMainAttributes(); 962 outputResources.remove(MANIFEST_NAME); 963 } 964 965 String createdBy = attribs.getValue(CREATED_BY); 966 if (createdBy == null) { 967 createdBy = ""; 968 } else { 969 createdBy += " + "; 970 } 971 createdBy += "dx " + Version.VERSION; 972 973 attribs.put(CREATED_BY, createdBy); 974 attribs.putValue("Dex-Location", DexFormat.DEX_IN_JAR_NAME); 975 976 return manifest; 977 } 978 979 /** 980 * Opens and returns the named file for writing, treating "-" specially. 981 * 982 * @param name {@code non-null;} the file name 983 * @return {@code non-null;} the opened file 984 */ openOutput(String name)985 private OutputStream openOutput(String name) throws IOException { 986 if (name.equals("-") || 987 name.startsWith("-.")) { 988 return context.out; 989 } 990 991 return new FileOutputStream(name); 992 } 993 994 /** 995 * Flushes and closes the given output stream, except if it happens to be 996 * {@link System#out} in which case this method does the flush but not 997 * the close. This method will also silently do nothing if given a 998 * {@code null} argument. 999 * 1000 * @param stream {@code null-ok;} what to close 1001 */ closeOutput(OutputStream stream)1002 private void closeOutput(OutputStream stream) throws IOException { 1003 if (stream == null) { 1004 return; 1005 } 1006 1007 stream.flush(); 1008 1009 if (stream != context.out) { 1010 stream.close(); 1011 } 1012 } 1013 1014 /** 1015 * Returns the "fixed" version of a given file path, suitable for 1016 * use as a path within a {@code .jar} file and for checking 1017 * against a classfile-internal "this class" name. This looks for 1018 * the last instance of the substring {@code "/./"} within 1019 * the path, and if it finds it, it takes the portion after to be 1020 * the fixed path. If that isn't found but the path starts with 1021 * {@code "./"}, then that prefix is removed and the rest is 1022 * return. If neither of these is the case, this method returns 1023 * its argument. 1024 * 1025 * @param path {@code non-null;} the path to "fix" 1026 * @return {@code non-null;} the fixed version (which might be the same as 1027 * the given {@code path}) 1028 */ fixPath(String path)1029 private static String fixPath(String path) { 1030 /* 1031 * If the path separator is \ (like on windows), we convert the 1032 * path to a standard '/' separated path. 1033 */ 1034 if (File.separatorChar == '\\') { 1035 path = path.replace('\\', '/'); 1036 } 1037 1038 int index = path.lastIndexOf("/./"); 1039 1040 if (index != -1) { 1041 return path.substring(index + 3); 1042 } 1043 1044 if (path.startsWith("./")) { 1045 return path.substring(2); 1046 } 1047 1048 return path; 1049 } 1050 1051 /** 1052 * Dumps any method with the given name in the given file. 1053 * 1054 * @param dex {@code non-null;} the dex file 1055 * @param fqName {@code non-null;} the fully-qualified name of the 1056 * method(s) 1057 * @param out {@code non-null;} where to dump to 1058 */ dumpMethod(DexFile dex, String fqName, OutputStreamWriter out)1059 private void dumpMethod(DexFile dex, String fqName, 1060 OutputStreamWriter out) { 1061 boolean wildcard = fqName.endsWith("*"); 1062 int lastDot = fqName.lastIndexOf('.'); 1063 1064 if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) { 1065 context.err.println("bogus fully-qualified method name: " + 1066 fqName); 1067 return; 1068 } 1069 1070 String className = fqName.substring(0, lastDot).replace('.', '/'); 1071 String methodName = fqName.substring(lastDot + 1); 1072 ClassDefItem clazz = dex.getClassOrNull(className); 1073 1074 if (clazz == null) { 1075 context.err.println("no such class: " + className); 1076 return; 1077 } 1078 1079 if (wildcard) { 1080 methodName = methodName.substring(0, methodName.length() - 1); 1081 } 1082 1083 ArrayList<EncodedMethod> allMeths = clazz.getMethods(); 1084 TreeMap<CstNat, EncodedMethod> meths = 1085 new TreeMap<CstNat, EncodedMethod>(); 1086 1087 /* 1088 * Figure out which methods to include in the output, and get them 1089 * all sorted, so that the printout code is robust with respect to 1090 * changes in the underlying order. 1091 */ 1092 for (EncodedMethod meth : allMeths) { 1093 String methName = meth.getName().getString(); 1094 if ((wildcard && methName.startsWith(methodName)) || 1095 (!wildcard && methName.equals(methodName))) { 1096 meths.put(meth.getRef().getNat(), meth); 1097 } 1098 } 1099 1100 if (meths.size() == 0) { 1101 context.err.println("no such method: " + fqName); 1102 return; 1103 } 1104 1105 PrintWriter pw = new PrintWriter(out); 1106 1107 for (EncodedMethod meth : meths.values()) { 1108 // TODO: Better stuff goes here, perhaps. 1109 meth.debugPrint(pw, args.verboseDump); 1110 1111 /* 1112 * The (default) source file is an attribute of the class, but 1113 * it's useful to see it in method dumps. 1114 */ 1115 CstString sourceFile = clazz.getSourceFile(); 1116 if (sourceFile != null) { 1117 pw.println(" source file: " + sourceFile.toQuoted()); 1118 } 1119 1120 Annotations methodAnnotations = 1121 clazz.getMethodAnnotations(meth.getRef()); 1122 AnnotationsList parameterAnnotations = 1123 clazz.getParameterAnnotations(meth.getRef()); 1124 1125 if (methodAnnotations != null) { 1126 pw.println(" method annotations:"); 1127 for (Annotation a : methodAnnotations.getAnnotations()) { 1128 pw.println(" " + a); 1129 } 1130 } 1131 1132 if (parameterAnnotations != null) { 1133 pw.println(" parameter annotations:"); 1134 int sz = parameterAnnotations.size(); 1135 for (int i = 0; i < sz; i++) { 1136 pw.println(" parameter " + i); 1137 Annotations annotations = parameterAnnotations.get(i); 1138 for (Annotation a : annotations.getAnnotations()) { 1139 pw.println(" " + a); 1140 } 1141 } 1142 } 1143 } 1144 1145 pw.flush(); 1146 } 1147 1148 private static class NotFilter implements FileNameFilter { 1149 private final FileNameFilter filter; 1150 NotFilter(FileNameFilter filter)1151 private NotFilter(FileNameFilter filter) { 1152 this.filter = filter; 1153 } 1154 1155 @Override accept(String path)1156 public boolean accept(String path) { 1157 return !filter.accept(path); 1158 } 1159 } 1160 1161 /** 1162 * Filters "module-info.class" out of the paths accepted by delegate. 1163 */ 1164 private static class RemoveModuleInfoFilter implements FileNameFilter { 1165 protected final FileNameFilter delegate; 1166 RemoveModuleInfoFilter(FileNameFilter delegate)1167 public RemoveModuleInfoFilter(FileNameFilter delegate) { 1168 this.delegate = delegate; 1169 } 1170 1171 @Override accept(String path)1172 public boolean accept(String path) { 1173 return delegate.accept(path) && !("module-info.class".equals(path)); 1174 } 1175 } 1176 1177 /** 1178 * A quick and accurate filter for when file path can be trusted. 1179 */ 1180 private class MainDexListFilter implements FileNameFilter { 1181 1182 @Override accept(String fullPath)1183 public boolean accept(String fullPath) { 1184 if (fullPath.endsWith(".class")) { 1185 String path = fixPath(fullPath); 1186 return classesInMainDex.contains(path); 1187 } else { 1188 return true; 1189 } 1190 } 1191 } 1192 1193 /** 1194 * A best effort conservative filter for when file path can <b>not</b> be trusted. 1195 */ 1196 private class BestEffortMainDexListFilter implements FileNameFilter { 1197 1198 Map<String, List<String>> map = new HashMap<String, List<String>>(); 1199 BestEffortMainDexListFilter()1200 public BestEffortMainDexListFilter() { 1201 for (String pathOfClass : classesInMainDex) { 1202 String normalized = fixPath(pathOfClass); 1203 String simple = getSimpleName(normalized); 1204 List<String> fullPath = map.get(simple); 1205 if (fullPath == null) { 1206 fullPath = new ArrayList<String>(1); 1207 map.put(simple, fullPath); 1208 } 1209 fullPath.add(normalized); 1210 } 1211 } 1212 1213 @Override accept(String path)1214 public boolean accept(String path) { 1215 if (path.endsWith(".class")) { 1216 String normalized = fixPath(path); 1217 String simple = getSimpleName(normalized); 1218 List<String> fullPaths = map.get(simple); 1219 if (fullPaths != null) { 1220 for (String fullPath : fullPaths) { 1221 if (normalized.endsWith(fullPath)) { 1222 return true; 1223 } 1224 } 1225 } 1226 return false; 1227 } else { 1228 return true; 1229 } 1230 } 1231 getSimpleName(String path)1232 private String getSimpleName(String path) { 1233 int index = path.lastIndexOf('/'); 1234 if (index >= 0) { 1235 return path.substring(index + 1); 1236 } else { 1237 return path; 1238 } 1239 } 1240 } 1241 1242 /** 1243 * Exception class used to halt processing prematurely. 1244 */ 1245 private static class StopProcessing extends RuntimeException { 1246 // This space intentionally left blank. 1247 } 1248 1249 /** 1250 * Command-line argument parser and access. 1251 */ 1252 public static class Arguments { 1253 1254 private static final String MINIMAL_MAIN_DEX_OPTION = "--minimal-main-dex"; 1255 1256 private static final String MAIN_DEX_LIST_OPTION = "--main-dex-list"; 1257 1258 private static final String MULTI_DEX_OPTION = "--multi-dex"; 1259 1260 private static final String NUM_THREADS_OPTION = "--num-threads"; 1261 1262 private static final String INCREMENTAL_OPTION = "--incremental"; 1263 1264 private static final String INPUT_LIST_OPTION = "--input-list"; 1265 1266 public final DxContext context; 1267 1268 /** whether to run in debug mode */ 1269 public boolean debug = false; 1270 1271 /** whether to emit warning messages */ 1272 public boolean warnings = true; 1273 1274 /** whether to emit high-level verbose human-oriented output */ 1275 public boolean verbose = false; 1276 1277 /** whether to emit verbose human-oriented output in the dump file */ 1278 public boolean verboseDump = false; 1279 1280 /** whether we are constructing a core library */ 1281 public boolean coreLibrary = false; 1282 1283 /** {@code null-ok;} particular method to dump */ 1284 public String methodToDump = null; 1285 1286 /** max width for columnar output */ 1287 public int dumpWidth = 0; 1288 1289 /** {@code null-ok;} output file name for binary file */ 1290 public String outName = null; 1291 1292 /** {@code null-ok;} output file name for human-oriented dump */ 1293 public String humanOutName = null; 1294 1295 /** whether strict file-name-vs-class-name checking should be done */ 1296 public boolean strictNameCheck = true; 1297 1298 /** 1299 * whether it is okay for there to be no {@code .class} files 1300 * to process 1301 */ 1302 public boolean emptyOk = false; 1303 1304 /** 1305 * whether the binary output is to be a {@code .jar} file 1306 * instead of a plain {@code .dex} 1307 */ 1308 public boolean jarOutput = false; 1309 1310 /** 1311 * when writing a {@code .jar} file, whether to still 1312 * keep the {@code .class} files 1313 */ 1314 public boolean keepClassesInJar = false; 1315 1316 /** what API level to target */ 1317 public int minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; 1318 1319 /** how much source position info to preserve */ 1320 public int positionInfo = PositionList.LINES; 1321 1322 /** whether to keep local variable information */ 1323 public boolean localInfo = true; 1324 1325 /** whether to merge with the output dex file if it exists. */ 1326 public boolean incremental = false; 1327 1328 /** whether to force generation of const-string/jumbo for all indexes, 1329 * to allow merges between dex files with many strings. */ 1330 public boolean forceJumbo = false; 1331 1332 /** whether default and static interface methods can be invoked at any API level. */ 1333 public boolean allowAllInterfaceMethodInvokes = false; 1334 1335 /** {@code non-null} after {@link #parse}; file name arguments */ 1336 public String[] fileNames; 1337 1338 /** whether to do SSA/register optimization */ 1339 public boolean optimize = true; 1340 1341 /** Filename containg list of methods to optimize */ 1342 public String optimizeListFile = null; 1343 1344 /** Filename containing list of methods to NOT optimize */ 1345 public String dontOptimizeListFile = null; 1346 1347 /** Whether to print statistics to stdout at end of compile cycle */ 1348 public boolean statistics; 1349 1350 /** Options for class file transformation */ 1351 public CfOptions cfOptions; 1352 1353 /** Options for dex file output */ 1354 public DexOptions dexOptions; 1355 1356 /** number of threads to run with */ 1357 public int numThreads = 1; 1358 1359 /** generation of multiple dex is allowed */ 1360 public boolean multiDex = false; 1361 1362 /** Optional file containing a list of class files containing classes to be forced in main 1363 * dex */ 1364 public String mainDexListFile = null; 1365 1366 /** Produce the smallest possible main dex. Ignored unless multiDex is true and 1367 * mainDexListFile is specified and non empty. */ 1368 public boolean minimalMainDex = false; 1369 1370 public int maxNumberOfIdxPerDex = DexFormat.MAX_MEMBER_IDX + 1; 1371 1372 /** Optional list containing inputs read in from a file. */ 1373 private List<String> inputList = null; 1374 1375 private boolean outputIsDirectory = false; 1376 private boolean outputIsDirectDex = false; 1377 Arguments(DxContext context)1378 public Arguments(DxContext context) { 1379 this.context = context; 1380 } 1381 Arguments()1382 public Arguments() { 1383 this(new DxContext()); 1384 } 1385 1386 private static class ArgumentsParser { 1387 1388 /** The arguments to process. */ 1389 private final String[] arguments; 1390 /** The index of the next argument to process. */ 1391 private int index; 1392 /** The current argument being processed after a {@link #getNext()} call. */ 1393 private String current; 1394 /** The last value of an argument processed by {@link #isArg(String)}. */ 1395 private String lastValue; 1396 ArgumentsParser(String[] arguments)1397 public ArgumentsParser(String[] arguments) { 1398 this.arguments = arguments; 1399 index = 0; 1400 } 1401 getCurrent()1402 public String getCurrent() { 1403 return current; 1404 } 1405 getLastValue()1406 public String getLastValue() { 1407 return lastValue; 1408 } 1409 1410 /** 1411 * Moves on to the next argument. 1412 * Returns false when we ran out of arguments that start with --. 1413 */ getNext()1414 public boolean getNext() { 1415 if (index >= arguments.length) { 1416 return false; 1417 } 1418 current = arguments[index]; 1419 if (current.equals("--") || !current.startsWith("--")) { 1420 return false; 1421 } 1422 index++; 1423 return true; 1424 } 1425 1426 /** 1427 * Similar to {@link #getNext()}, this moves on the to next argument. 1428 * It does not check however whether the argument starts with -- 1429 * and thus can be used to retrieve values. 1430 */ getNextValue()1431 private boolean getNextValue() { 1432 if (index >= arguments.length) { 1433 return false; 1434 } 1435 current = arguments[index]; 1436 index++; 1437 return true; 1438 } 1439 1440 /** 1441 * Returns all the arguments that have not been processed yet. 1442 */ getRemaining()1443 public String[] getRemaining() { 1444 int n = arguments.length - index; 1445 String[] remaining = new String[n]; 1446 if (n > 0) { 1447 System.arraycopy(arguments, index, remaining, 0, n); 1448 } 1449 return remaining; 1450 } 1451 1452 /** 1453 * Checks the current argument against the given prefix. 1454 * If prefix is in the form '--name=', an extra value is expected. 1455 * The argument can then be in the form '--name=value' or as a 2-argument 1456 * form '--name value'. 1457 */ isArg(String prefix)1458 public boolean isArg(String prefix) { 1459 int n = prefix.length(); 1460 if (n > 0 && prefix.charAt(n-1) == '=') { 1461 // Argument accepts a value. Capture it. 1462 if (current.startsWith(prefix)) { 1463 // Argument is in the form --name=value, split the value out 1464 lastValue = current.substring(n); 1465 return true; 1466 } else { 1467 // Check whether we have "--name value" as 2 arguments 1468 prefix = prefix.substring(0, n-1); 1469 if (current.equals(prefix)) { 1470 if (getNextValue()) { 1471 lastValue = current; 1472 return true; 1473 } else { 1474 System.err.println("Missing value after parameter " + prefix); 1475 throw new UsageException(); 1476 } 1477 } 1478 return false; 1479 } 1480 } else { 1481 // Argument does not accept a value. 1482 return current.equals(prefix); 1483 } 1484 } 1485 } 1486 parseFlags(ArgumentsParser parser)1487 private void parseFlags(ArgumentsParser parser) { 1488 1489 while(parser.getNext()) { 1490 if (parser.isArg("--debug")) { 1491 debug = true; 1492 } else if (parser.isArg("--no-warning")) { 1493 warnings = false; 1494 } else if (parser.isArg("--verbose")) { 1495 verbose = true; 1496 } else if (parser.isArg("--verbose-dump")) { 1497 verboseDump = true; 1498 } else if (parser.isArg("--no-files")) { 1499 emptyOk = true; 1500 } else if (parser.isArg("--no-optimize")) { 1501 optimize = false; 1502 } else if (parser.isArg("--no-strict")) { 1503 strictNameCheck = false; 1504 } else if (parser.isArg("--core-library")) { 1505 coreLibrary = true; 1506 } else if (parser.isArg("--statistics")) { 1507 statistics = true; 1508 } else if (parser.isArg("--optimize-list=")) { 1509 if (dontOptimizeListFile != null) { 1510 context.err.println("--optimize-list and " 1511 + "--no-optimize-list are incompatible."); 1512 throw new UsageException(); 1513 } 1514 optimize = true; 1515 optimizeListFile = parser.getLastValue(); 1516 } else if (parser.isArg("--no-optimize-list=")) { 1517 if (dontOptimizeListFile != null) { 1518 context.err.println("--optimize-list and " 1519 + "--no-optimize-list are incompatible."); 1520 throw new UsageException(); 1521 } 1522 optimize = true; 1523 dontOptimizeListFile = parser.getLastValue(); 1524 } else if (parser.isArg("--keep-classes")) { 1525 keepClassesInJar = true; 1526 } else if (parser.isArg("--output=")) { 1527 outName = parser.getLastValue(); 1528 if (new File(outName).isDirectory()) { 1529 jarOutput = false; 1530 outputIsDirectory = true; 1531 } else if (FileUtils.hasArchiveSuffix(outName)) { 1532 jarOutput = true; 1533 } else if (outName.endsWith(".dex") || 1534 outName.equals("-")) { 1535 jarOutput = false; 1536 outputIsDirectDex = true; 1537 } else { 1538 context.err.println("unknown output extension: " + 1539 outName); 1540 throw new UsageException(); 1541 } 1542 } else if (parser.isArg("--dump-to=")) { 1543 humanOutName = parser.getLastValue(); 1544 } else if (parser.isArg("--dump-width=")) { 1545 dumpWidth = Integer.parseInt(parser.getLastValue()); 1546 } else if (parser.isArg("--dump-method=")) { 1547 methodToDump = parser.getLastValue(); 1548 jarOutput = false; 1549 } else if (parser.isArg("--positions=")) { 1550 String pstr = parser.getLastValue().intern(); 1551 if (pstr == "none") { 1552 positionInfo = PositionList.NONE; 1553 } else if (pstr == "important") { 1554 positionInfo = PositionList.IMPORTANT; 1555 } else if (pstr == "lines") { 1556 positionInfo = PositionList.LINES; 1557 } else { 1558 context.err.println("unknown positions option: " + 1559 pstr); 1560 throw new UsageException(); 1561 } 1562 } else if (parser.isArg("--no-locals")) { 1563 localInfo = false; 1564 } else if (parser.isArg(NUM_THREADS_OPTION + "=")) { 1565 numThreads = Integer.parseInt(parser.getLastValue()); 1566 } else if (parser.isArg(INCREMENTAL_OPTION)) { 1567 incremental = true; 1568 } else if (parser.isArg("--force-jumbo")) { 1569 forceJumbo = true; 1570 } else if (parser.isArg(MULTI_DEX_OPTION)) { 1571 multiDex = true; 1572 } else if (parser.isArg(MAIN_DEX_LIST_OPTION + "=")) { 1573 mainDexListFile = parser.getLastValue(); 1574 } else if (parser.isArg(MINIMAL_MAIN_DEX_OPTION)) { 1575 minimalMainDex = true; 1576 } else if (parser.isArg("--set-max-idx-number=")) { // undocumented test option 1577 maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue()); 1578 } else if(parser.isArg(INPUT_LIST_OPTION + "=")) { 1579 File inputListFile = new File(parser.getLastValue()); 1580 try { 1581 inputList = new ArrayList<String>(); 1582 readPathsFromFile(inputListFile.getAbsolutePath(), inputList); 1583 } catch (IOException e) { 1584 context.err.println( 1585 "Unable to read input list file: " + inputListFile.getName()); 1586 // problem reading the file so we should halt execution 1587 throw new UsageException(); 1588 } 1589 } else if (parser.isArg("--min-sdk-version=")) { 1590 String arg = parser.getLastValue(); 1591 int value; 1592 try { 1593 value = Integer.parseInt(arg); 1594 } catch (NumberFormatException ex) { 1595 value = -1; 1596 } 1597 if (value < 1) { 1598 System.err.println("improper min-sdk-version option: " + arg); 1599 throw new UsageException(); 1600 } 1601 minSdkVersion = value; 1602 } else if (parser.isArg("--allow-all-interface-method-invokes")) { 1603 allowAllInterfaceMethodInvokes = true; 1604 } else { 1605 context.err.println("unknown option: " + parser.getCurrent()); 1606 throw new UsageException(); 1607 } 1608 } 1609 } 1610 1611 1612 /** 1613 * Parses all command-line arguments and updates the state of the {@code Arguments} object 1614 * accordingly. 1615 * 1616 * @param args {@code non-null;} the arguments 1617 */ parse(String[] args)1618 private void parse(String[] args) { 1619 ArgumentsParser parser = new ArgumentsParser(args); 1620 1621 parseFlags(parser); 1622 1623 fileNames = parser.getRemaining(); 1624 if(inputList != null && !inputList.isEmpty()) { 1625 // append the file names to the end of the input list 1626 inputList.addAll(Arrays.asList(fileNames)); 1627 fileNames = inputList.toArray(new String[inputList.size()]); 1628 } 1629 1630 if (fileNames.length == 0) { 1631 if (!emptyOk) { 1632 context.err.println("no input files specified"); 1633 throw new UsageException(); 1634 } 1635 } else if (emptyOk) { 1636 context.out.println("ignoring input files"); 1637 } 1638 1639 if ((humanOutName == null) && (methodToDump != null)) { 1640 humanOutName = "-"; 1641 } 1642 1643 if (mainDexListFile != null && !multiDex) { 1644 context.err.println(MAIN_DEX_LIST_OPTION + " is only supported in combination with " 1645 + MULTI_DEX_OPTION); 1646 throw new UsageException(); 1647 } 1648 1649 if (minimalMainDex && (mainDexListFile == null || !multiDex)) { 1650 context.err.println(MINIMAL_MAIN_DEX_OPTION + " is only supported in combination with " 1651 + MULTI_DEX_OPTION + " and " + MAIN_DEX_LIST_OPTION); 1652 throw new UsageException(); 1653 } 1654 1655 if (multiDex && incremental) { 1656 context.err.println(INCREMENTAL_OPTION + " is not supported with " 1657 + MULTI_DEX_OPTION); 1658 throw new UsageException(); 1659 } 1660 1661 if (multiDex && outputIsDirectDex) { 1662 context.err.println("Unsupported output \"" + outName +"\". " + MULTI_DEX_OPTION + 1663 " supports only archive or directory output"); 1664 throw new UsageException(); 1665 } 1666 1667 if (outputIsDirectory && !multiDex) { 1668 outName = new File(outName, DexFormat.DEX_IN_JAR_NAME).getPath(); 1669 } 1670 1671 makeOptionsObjects(); 1672 } 1673 1674 /** 1675 * Parses only command-line flags and updates the state of the {@code Arguments} object 1676 * accordingly. 1677 * 1678 * @param flags {@code non-null;} the flags 1679 */ parseFlags(String[] flags)1680 public void parseFlags(String[] flags) { 1681 parseFlags(new ArgumentsParser(flags)); 1682 } 1683 1684 /** 1685 * Copies relevant arguments over into CfOptions and DexOptions instances. 1686 */ makeOptionsObjects()1687 public void makeOptionsObjects() { 1688 cfOptions = new CfOptions(); 1689 cfOptions.positionInfo = positionInfo; 1690 cfOptions.localInfo = localInfo; 1691 cfOptions.strictNameCheck = strictNameCheck; 1692 cfOptions.optimize = optimize; 1693 cfOptions.optimizeListFile = optimizeListFile; 1694 cfOptions.dontOptimizeListFile = dontOptimizeListFile; 1695 cfOptions.statistics = statistics; 1696 1697 if (warnings) { 1698 cfOptions.warn = context.err; 1699 } else { 1700 cfOptions.warn = context.noop; 1701 } 1702 1703 dexOptions = new DexOptions(context.err); 1704 dexOptions.minSdkVersion = minSdkVersion; 1705 dexOptions.forceJumbo = forceJumbo; 1706 dexOptions.allowAllInterfaceMethodInvokes = allowAllInterfaceMethodInvokes; 1707 } 1708 } 1709 1710 /** 1711 * Callback class for processing input file bytes, produced by the 1712 * ClassPathOpener. 1713 */ 1714 private class FileBytesConsumer implements ClassPathOpener.Consumer { 1715 1716 @Override processFileBytes(String name, long lastModified, byte[] bytes)1717 public boolean processFileBytes(String name, long lastModified, 1718 byte[] bytes) { 1719 return Main.this.processFileBytes(name, lastModified, bytes); 1720 } 1721 1722 @Override onException(Exception ex)1723 public void onException(Exception ex) { 1724 if (ex instanceof StopProcessing) { 1725 throw (StopProcessing) ex; 1726 } else if (ex instanceof SimException) { 1727 context.err.println("\nEXCEPTION FROM SIMULATION:"); 1728 context.err.println(ex.getMessage() + "\n"); 1729 context.err.println(((SimException) ex).getContext()); 1730 } else if (ex instanceof ParseException) { 1731 context.err.println("\nPARSE ERROR:"); 1732 ParseException parseException = (ParseException) ex; 1733 if (args.debug) { 1734 parseException.printStackTrace(context.err); 1735 } else { 1736 parseException.printContext(context.err); 1737 } 1738 } else { 1739 context.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 1740 ex.printStackTrace(context.err); 1741 } 1742 errors.incrementAndGet(); 1743 } 1744 1745 @Override onProcessArchiveStart(File file)1746 public void onProcessArchiveStart(File file) { 1747 if (args.verbose) { 1748 context.out.println("processing archive " + file + "..."); 1749 } 1750 } 1751 } 1752 1753 /** Callable helper class to parse class bytes. */ 1754 private class ClassParserTask implements Callable<DirectClassFile> { 1755 1756 String name; 1757 byte[] bytes; 1758 ClassParserTask(String name, byte[] bytes)1759 private ClassParserTask(String name, byte[] bytes) { 1760 this.name = name; 1761 this.bytes = bytes; 1762 } 1763 1764 @Override call()1765 public DirectClassFile call() throws Exception { 1766 DirectClassFile cf = parseClass(name, bytes); 1767 1768 return cf; 1769 } 1770 } 1771 1772 /** 1773 * Callable helper class used to sequentially collect the results of 1774 * the (optionally parallel) translation phase, in correct input file order. 1775 * This class is also responsible for coordinating dex file rotation 1776 * with the ClassDefItemConsumer class. 1777 * We maintain invariant that the number of indices used in the current 1778 * dex file plus the max number of indices required by classes passed to 1779 * the translation phase and not yet added to the dex file, is less than 1780 * or equal to the dex file limit. 1781 * For each parsed file, we estimate the maximum number of indices it may 1782 * require. If passing the file to the translation phase would invalidate 1783 * the invariant, we wait, until the next class is added to the dex file, 1784 * and then reevaluate the invariant. If there are no further classes in 1785 * the translation phase, we rotate the dex file. 1786 */ 1787 private class DirectClassFileConsumer implements Callable<Boolean> { 1788 1789 String name; 1790 byte[] bytes; 1791 Future<DirectClassFile> dcff; 1792 DirectClassFileConsumer(String name, byte[] bytes, Future<DirectClassFile> dcff)1793 private DirectClassFileConsumer(String name, byte[] bytes, 1794 Future<DirectClassFile> dcff) { 1795 this.name = name; 1796 this.bytes = bytes; 1797 this.dcff = dcff; 1798 } 1799 1800 @Override call()1801 public Boolean call() throws Exception { 1802 1803 DirectClassFile cf = dcff.get(); 1804 return call(cf); 1805 } 1806 call(DirectClassFile cf)1807 private Boolean call(DirectClassFile cf) { 1808 1809 int maxMethodIdsInClass = 0; 1810 int maxFieldIdsInClass = 0; 1811 1812 if (args.multiDex) { 1813 1814 // Calculate max number of indices this class will add to the 1815 // dex file. 1816 // The possibility of overloading means that we can't easily 1817 // know how many constant are needed for declared methods and 1818 // fields. We therefore make the simplifying assumption that 1819 // all constants are external method or field references. 1820 1821 int constantPoolSize = cf.getConstantPool().size(); 1822 maxMethodIdsInClass = constantPoolSize + cf.getMethods().size() 1823 + MAX_METHOD_ADDED_DURING_DEX_CREATION; 1824 maxFieldIdsInClass = constantPoolSize + cf.getFields().size() 1825 + MAX_FIELD_ADDED_DURING_DEX_CREATION; 1826 synchronized(dexRotationLock) { 1827 1828 int numMethodIds; 1829 int numFieldIds; 1830 // Number of indices used in current dex file. 1831 synchronized(outputDex) { 1832 numMethodIds = outputDex.getMethodIds().items().size(); 1833 numFieldIds = outputDex.getFieldIds().items().size(); 1834 } 1835 // Wait until we're sure this class will fit in the current 1836 // dex file. 1837 while(((numMethodIds + maxMethodIdsInClass + maxMethodIdsInProcess 1838 > args.maxNumberOfIdxPerDex) || 1839 (numFieldIds + maxFieldIdsInClass + maxFieldIdsInProcess 1840 > args.maxNumberOfIdxPerDex))) { 1841 1842 if (maxMethodIdsInProcess > 0 || maxFieldIdsInProcess > 0) { 1843 // There are classes in the translation phase that 1844 // have not yet been added to the dex file, so we 1845 // wait for the next class to complete. 1846 try { 1847 dexRotationLock.wait(); 1848 } catch(InterruptedException ex) { 1849 /* ignore */ 1850 } 1851 } else if (outputDex.getClassDefs().items().size() > 0) { 1852 // There are no further classes in the translation 1853 // phase, and we have a full dex file. Rotate! 1854 rotateDexFile(); 1855 } else { 1856 // The estimated number of indices is too large for 1857 // an empty dex file. We proceed hoping the actual 1858 // number of indices needed will fit. 1859 break; 1860 } 1861 synchronized(outputDex) { 1862 numMethodIds = outputDex.getMethodIds().items().size(); 1863 numFieldIds = outputDex.getFieldIds().items().size(); 1864 } 1865 } 1866 // Add our estimate to the total estimate for 1867 // classes under translation. 1868 maxMethodIdsInProcess += maxMethodIdsInClass; 1869 maxFieldIdsInProcess += maxFieldIdsInClass; 1870 } 1871 } 1872 1873 // Submit class to translation phase. 1874 Future<ClassDefItem> cdif = classTranslatorPool.submit( 1875 new ClassTranslatorTask(name, bytes, cf)); 1876 Future<Boolean> res = classDefItemConsumer.submit(new ClassDefItemConsumer( 1877 name, cdif, maxMethodIdsInClass, maxFieldIdsInClass)); 1878 addToDexFutures.add(res); 1879 1880 return true; 1881 } 1882 } 1883 1884 1885 /** Callable helper class to translate classes in parallel */ 1886 private class ClassTranslatorTask implements Callable<ClassDefItem> { 1887 1888 String name; 1889 byte[] bytes; 1890 DirectClassFile classFile; 1891 ClassTranslatorTask(String name, byte[] bytes, DirectClassFile classFile)1892 private ClassTranslatorTask(String name, byte[] bytes, 1893 DirectClassFile classFile) { 1894 this.name = name; 1895 this.bytes = bytes; 1896 this.classFile = classFile; 1897 } 1898 1899 @Override call()1900 public ClassDefItem call() { 1901 ClassDefItem clazz = translateClass(bytes, classFile); 1902 return clazz; 1903 } 1904 } 1905 1906 /** 1907 * Callable helper class used to collect the results of 1908 * the parallel translation phase, adding the translated classes to 1909 * the current dex file in correct (deterministic) file order. 1910 * This class is also responsible for coordinating dex file rotation 1911 * with the DirectClassFileConsumer class. 1912 */ 1913 private class ClassDefItemConsumer implements Callable<Boolean> { 1914 1915 String name; 1916 Future<ClassDefItem> futureClazz; 1917 int maxMethodIdsInClass; 1918 int maxFieldIdsInClass; 1919 ClassDefItemConsumer(String name, Future<ClassDefItem> futureClazz, int maxMethodIdsInClass, int maxFieldIdsInClass)1920 private ClassDefItemConsumer(String name, Future<ClassDefItem> futureClazz, 1921 int maxMethodIdsInClass, int maxFieldIdsInClass) { 1922 this.name = name; 1923 this.futureClazz = futureClazz; 1924 this.maxMethodIdsInClass = maxMethodIdsInClass; 1925 this.maxFieldIdsInClass = maxFieldIdsInClass; 1926 } 1927 1928 @Override call()1929 public Boolean call() throws Exception { 1930 try { 1931 ClassDefItem clazz = futureClazz.get(); 1932 if (clazz != null) { 1933 addClassToDex(clazz); 1934 updateStatus(true); 1935 } 1936 return true; 1937 } catch(ExecutionException ex) { 1938 // Rethrow previously uncaught translation exceptions. 1939 // These, as well as any exceptions from addClassToDex, 1940 // are handled and reported in processAllFiles(). 1941 Throwable t = ex.getCause(); 1942 throw (t instanceof Exception) ? (Exception) t : ex; 1943 } finally { 1944 if (args.multiDex) { 1945 // Having added our actual indicies to the dex file, 1946 // we subtract our original estimate from the total estimate, 1947 // and signal the translation phase, which may be paused 1948 // waiting to determine if more classes can be added to the 1949 // current dex file, or if a new dex file must be created. 1950 synchronized(dexRotationLock) { 1951 maxMethodIdsInProcess -= maxMethodIdsInClass; 1952 maxFieldIdsInProcess -= maxFieldIdsInClass; 1953 dexRotationLock.notifyAll(); 1954 } 1955 } 1956 } 1957 } 1958 } 1959 1960 /** Callable helper class to convert dex files in worker threads */ 1961 private class DexWriter implements Callable<byte[]> { 1962 1963 private final DexFile dexFile; 1964 DexWriter(DexFile dexFile)1965 private DexWriter(DexFile dexFile) { 1966 this.dexFile = dexFile; 1967 } 1968 1969 @Override call()1970 public byte[] call() throws IOException { 1971 return writeDex(dexFile); 1972 } 1973 } 1974 } 1975