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