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