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