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 &gt; 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