• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.dx.Version;
20 import com.android.dx.cf.iface.ParseException;
21 import com.android.dx.cf.direct.ClassPathOpener;
22 import com.android.dx.command.DxConsole;
23 import com.android.dx.command.UsageException;
24 import com.android.dx.dex.cf.CfOptions;
25 import com.android.dx.dex.cf.CfTranslator;
26 import com.android.dx.dex.cf.CodeStatistics;
27 import com.android.dx.dex.code.PositionList;
28 import com.android.dx.dex.file.ClassDefItem;
29 import com.android.dx.dex.file.DexFile;
30 import com.android.dx.dex.file.EncodedMethod;
31 import com.android.dx.rop.annotation.Annotation;
32 import com.android.dx.rop.annotation.Annotations;
33 import com.android.dx.rop.annotation.AnnotationsList;
34 import com.android.dx.rop.cst.CstNat;
35 import com.android.dx.rop.cst.CstUtf8;
36 
37 import java.io.ByteArrayInputStream;
38 import java.io.File;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.OutputStream;
42 import java.io.OutputStreamWriter;
43 import java.io.PrintWriter;
44 import java.util.Arrays;
45 import java.util.ArrayList;
46 import java.util.Map;
47 import java.util.TreeMap;
48 import java.util.jar.Attributes;
49 import java.util.jar.JarEntry;
50 import java.util.jar.JarOutputStream;
51 import java.util.jar.Manifest;
52 
53 /**
54  * Main class for the class file translator.
55  */
56 public class Main {
57     /**
58      * {@code non-null;} name for the {@code .dex} file that goes into
59      * {@code .jar} files
60      */
61     private static final String DEX_IN_JAR_NAME = "classes.dex";
62 
63     /**
64      * {@code non-null;} name of the standard manifest file in {@code .jar}
65      * files
66      */
67     private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
68 
69     /**
70      * {@code non-null;} attribute name for the (quasi-standard?)
71      * {@code Created-By} attribute
72      */
73     private static final Attributes.Name CREATED_BY =
74         new Attributes.Name("Created-By");
75 
76     /**
77      * {@code non-null;} list of {@code javax} subpackages that are considered
78      * to be "core". <b>Note:</b>: This list must be sorted, since it
79      * is binary-searched.
80      */
81     private static final String[] JAVAX_CORE = {
82         "accessibility", "crypto", "imageio", "management", "naming", "net",
83         "print", "rmi", "security", "sound", "sql", "swing", "transaction",
84         "xml"
85     };
86 
87     /** number of warnings during processing */
88     private static int warnings = 0;
89 
90     /** number of errors during processing */
91     private static int errors = 0;
92 
93     /** {@code non-null;} parsed command-line arguments */
94     private static Arguments args;
95 
96     /** {@code non-null;} output file in-progress */
97     private static DexFile outputDex;
98 
99     /**
100      * {@code null-ok;} map of resources to include in the output, or
101      * {@code null} if resources are being ignored
102      */
103     private static TreeMap<String, byte[]> outputResources;
104 
105     /**
106      * This class is uninstantiable.
107      */
Main()108     private Main() {
109         // This space intentionally left blank.
110     }
111 
112     /**
113      * Run and exit if something unexpected happened.
114      * @param argArray the command line arguments
115      */
main(String[] argArray)116     public static void main(String[] argArray) {
117         Arguments arguments = new Arguments();
118         arguments.parse(argArray);
119 
120         int result = run(arguments);
121         if (result != 0) {
122             System.exit(result);
123         }
124     }
125 
126     /**
127      * Run and return a result code.
128      * @param arguments the data + parameters for the conversion
129      * @return 0 if success > 0 otherwise.
130      */
run(Arguments arguments)131     public static int run(Arguments arguments) {
132         // Reset the error/warning count to start fresh.
133         warnings = 0;
134         errors = 0;
135 
136         args = arguments;
137         args.makeCfOptions();
138 
139         if (!processAllFiles()) {
140             return 1;
141         }
142 
143         byte[] outArray = writeDex();
144 
145         if (outArray == null) {
146             return 2;
147         }
148 
149         if (args.jarOutput) {
150             // Effectively free up the (often massive) DexFile memory.
151             outputDex = null;
152 
153             if (!createJar(args.outName, outArray)) {
154                 return 3;
155             }
156         }
157 
158         return 0;
159     }
160 
161     /**
162      * Constructs the output {@link DexFile}, fill it in with all the
163      * specified classes, and populate the resources map if required.
164      *
165      * @return whether processing was successful
166      */
processAllFiles()167     private static boolean processAllFiles() {
168         outputDex = new DexFile();
169 
170         if (args.jarOutput) {
171             outputResources = new TreeMap<String, byte[]>();
172         }
173 
174         if (args.dumpWidth != 0) {
175             outputDex.setDumpWidth(args.dumpWidth);
176         }
177 
178         boolean any = false;
179         String[] fileNames = args.fileNames;
180 
181         try {
182             for (int i = 0; i < fileNames.length; i++) {
183                 any |= processOne(fileNames[i]);
184             }
185         } catch (StopProcessing ex) {
186             /*
187              * Ignore it and just let the warning/error reporting do
188              * their things.
189              */
190         }
191 
192         if (warnings != 0) {
193             DxConsole.err.println(warnings + " warning" +
194                                ((warnings == 1) ? "" : "s"));
195         }
196 
197         if (errors != 0) {
198             DxConsole.err.println(errors + " error" +
199                     ((errors == 1) ? "" : "s") + "; aborting");
200             return false;
201         }
202 
203         if (!(any || args.emptyOk)) {
204             DxConsole.err.println("no classfiles specified");
205             return false;
206         }
207 
208         if (args.optimize && args.statistics) {
209             CodeStatistics.dumpStatistics(DxConsole.out);
210         }
211 
212         return true;
213     }
214 
215     /**
216      * Processes one pathname element.
217      *
218      * @param pathname {@code non-null;} the pathname to process. May
219      * be the path of a class file, a jar file, or a directory
220      * containing class files.
221      * @return whether any processing actually happened
222      */
processOne(String pathname)223     private static boolean processOne(String pathname) {
224         ClassPathOpener opener;
225 
226         opener = new ClassPathOpener(pathname, false,
227                 new ClassPathOpener.Consumer() {
228             public boolean processFileBytes(String name, byte[] bytes) {
229                 return Main.processFileBytes(name, bytes);
230             }
231             public void onException(Exception ex) {
232                 if (ex instanceof StopProcessing) {
233                     throw (StopProcessing) ex;
234                 }
235                 DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
236                 ex.printStackTrace(DxConsole.err);
237                 errors++;
238             }
239             public void onProcessArchiveStart(File file) {
240                 if (args.verbose) {
241                     DxConsole.out.println("processing archive " + file +
242                             "...");
243                 }
244             }
245         });
246 
247         return opener.process();
248     }
249 
250     /**
251      * Processes one file, which may be either a class or a resource.
252      *
253      * @param name {@code non-null;} name of the file
254      * @param bytes {@code non-null;} contents of the file
255      * @return whether processing was successful
256      */
processFileBytes(String name, byte[] bytes)257     private static boolean processFileBytes(String name, byte[] bytes) {
258         boolean isClass = name.endsWith(".class");
259         boolean keepResources = (outputResources != null);
260 
261         if (!isClass && !keepResources) {
262             if (args.verbose) {
263                 DxConsole.out.println("ignored resource " + name);
264             }
265             return false;
266         }
267 
268         if (args.verbose) {
269             DxConsole.out.println("processing " + name + "...");
270         }
271 
272         String fixedName = fixPath(name);
273 
274         if (isClass) {
275             if (keepResources && args.keepClassesInJar) {
276                 outputResources.put(fixedName, bytes);
277             }
278             return processClass(fixedName, bytes);
279         } else {
280             outputResources.put(fixedName, bytes);
281             return true;
282         }
283     }
284 
285     /**
286      * Processes one classfile.
287      *
288      * @param name {@code non-null;} name of the file, clipped such that it
289      * <i>should</i> correspond to the name of the class it contains
290      * @param bytes {@code non-null;} contents of the file
291      * @return whether processing was successful
292      */
processClass(String name, byte[] bytes)293     private static boolean processClass(String name, byte[] bytes) {
294         if (! args.coreLibrary) {
295             checkClassName(name);
296         }
297 
298         try {
299             ClassDefItem clazz =
300                 CfTranslator.translate(name, bytes, args.cfOptions);
301             outputDex.add(clazz);
302             return true;
303         } catch (ParseException ex) {
304             DxConsole.err.println("\ntrouble processing:");
305             if (args.debug) {
306                 ex.printStackTrace(DxConsole.err);
307             } else {
308                 ex.printContext(DxConsole.err);
309             }
310         }
311 
312         warnings++;
313         return false;
314     }
315 
316     /**
317      * Check the class name to make sure it's not a "core library"
318      * class. If there is a problem, this updates the error count and
319      * throws an exception to stop processing.
320      *
321      * @param name {@code non-null;} the fully-qualified internal-form
322      * class name
323      */
checkClassName(String name)324     private static void checkClassName(String name) {
325         boolean bogus = false;
326 
327         if (name.startsWith("java/")) {
328             bogus = true;
329         } else if (name.startsWith("javax/")) {
330             int slashAt = name.indexOf('/', 6);
331             if (slashAt == -1) {
332                 // Top-level javax classes are verboten.
333                 bogus = true;
334             } else {
335                 String pkg = name.substring(6, slashAt);
336                 bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0);
337             }
338         }
339 
340         if (! bogus) {
341             return;
342         }
343 
344         /*
345          * The user is probably trying to include an entire desktop
346          * core library in a misguided attempt to get their application
347          * working. Try to help them understand what's happening.
348          */
349 
350         DxConsole.err.println("\ntrouble processing \"" + name + "\":");
351         DxConsole.err.println("\n" +
352                 "Attempt to include a core class (java.* or javax.*) in " +
353                 "something other\n" +
354                 "than a core library. It is likely that you have " +
355                 "attempted to include\n" +
356                 "in an application the core library (or a part thereof) " +
357                 "from a desktop\n" +
358                 "virtual machine. This will most assuredly not work. " +
359                 "At a minimum, it\n" +
360                 "jeopardizes the compatibility of your app with future " +
361                 "versions of the\n" +
362                 "platform. It is also often of questionable legality.\n" +
363                 "\n" +
364                 "If you really intend to build a core library -- which is " +
365                 "only\n" +
366                 "appropriate as part of creating a full virtual machine " +
367                 "distribution,\n" +
368                 "as opposed to compiling an application -- then use the\n" +
369                 "\"--core-library\" option to suppress this error message.\n" +
370                 "\n" +
371                 "If you go ahead and use \"--core-library\" but are in " +
372                 "fact building an\n" +
373                 "application, then be forewarned that your application " +
374                 "will still fail\n" +
375                 "to build or run, at some point. Please be prepared for " +
376                 "angry customers\n" +
377                 "who find, for example, that your application ceases to " +
378                 "function once\n" +
379                 "they upgrade their operating system. You will be to " +
380                 "blame for this\n" +
381                 "problem.\n" +
382                 "\n" +
383                 "If you are legitimately using some code that happens to " +
384                 "be in a core\n" +
385                 "package, then the easiest safe alternative you have is " +
386                 "to repackage\n" +
387                 "that code. That is, move the classes in question into " +
388                 "your own package\n" +
389                 "namespace. This means that they will never be in " +
390                 "conflict with core\n" +
391                 "system classes. If you find that you cannot do this, " +
392                 "then that is an\n" +
393                 "indication that the path you are on will ultimately lead " +
394                 "to pain,\n" +
395                 "suffering, grief, and lamentation.\n");
396         errors++;
397         throw new StopProcessing();
398     }
399 
400     /**
401      * Converts {@link #outputDex} into a {@code byte[]}, write
402      * it out to the proper file (if any), and also do whatever human-oriented
403      * dumping is required.
404      *
405      * @return {@code null-ok;} the converted {@code byte[]} or {@code null}
406      * if there was a problem
407      */
writeDex()408     private static byte[] writeDex() {
409         byte[] outArray = null;
410 
411         try {
412             OutputStream out = null;
413             OutputStream humanOutRaw = null;
414             OutputStreamWriter humanOut = null;
415             try {
416                 if (args.humanOutName != null) {
417                     humanOutRaw = openOutput(args.humanOutName);
418                     humanOut = new OutputStreamWriter(humanOutRaw);
419                 }
420 
421                 if (args.methodToDump != null) {
422                     /*
423                      * Simply dump the requested method. Note: The call
424                      * to toDex() is required just to get the underlying
425                      * structures ready.
426                      */
427                     outputDex.toDex(null, false);
428                     dumpMethod(outputDex, args.methodToDump, humanOut);
429                 } else {
430                     /*
431                      * This is the usual case: Create an output .dex file,
432                      * and write it, dump it, etc.
433                      */
434                     outArray = outputDex.toDex(humanOut, args.verboseDump);
435 
436                     if ((args.outName != null) && !args.jarOutput) {
437                         out = openOutput(args.outName);
438                         out.write(outArray);
439                     }
440                 }
441 
442                 if (args.statistics) {
443                     DxConsole.out.println(outputDex.getStatistics().toHuman());
444                 }
445             } finally {
446                 if (humanOut != null) {
447                     humanOut.flush();
448                 }
449                 closeOutput(out);
450                 closeOutput(humanOutRaw);
451             }
452         } catch (Exception ex) {
453             if (args.debug) {
454                 DxConsole.err.println("\ntrouble writing output:");
455                 ex.printStackTrace(DxConsole.err);
456             } else {
457                 DxConsole.err.println("\ntrouble writing output: " +
458                                    ex.getMessage());
459             }
460             return null;
461         }
462 
463         return outArray;
464     }
465 
466     /**
467      * Creates a jar file from the resources and given dex file array.
468      *
469      * @param fileName {@code non-null;} name of the file
470      * @param dexArray {@code non-null;} array containing the dex file
471      * to include
472      * @return whether the creation was successful
473      */
createJar(String fileName, byte[] dexArray)474     private static boolean createJar(String fileName, byte[] dexArray) {
475         /*
476          * Make or modify the manifest (as appropriate), put the dex
477          * array into the resources map, and then process the entire
478          * resources map in a uniform manner.
479          */
480 
481         try {
482             Manifest manifest = makeManifest();
483             OutputStream out = openOutput(fileName);
484             JarOutputStream jarOut = new JarOutputStream(out, manifest);
485 
486             outputResources.put(DEX_IN_JAR_NAME, dexArray);
487 
488             try {
489                 for (Map.Entry<String, byte[]> e :
490                          outputResources.entrySet()) {
491                     String name = e.getKey();
492                     byte[] contents = e.getValue();
493                     JarEntry entry = new JarEntry(name);
494 
495                     if (args.verbose) {
496                         DxConsole.out.println("writing " + name + "; size " +
497                                            contents.length + "...");
498                     }
499 
500                     entry.setSize(contents.length);
501                     jarOut.putNextEntry(entry);
502                     jarOut.write(contents);
503                     jarOut.closeEntry();
504                 }
505             } finally {
506                 jarOut.finish();
507                 jarOut.flush();
508                 closeOutput(out);
509             }
510         } catch (Exception ex) {
511             if (args.debug) {
512                 DxConsole.err.println("\ntrouble writing output:");
513                 ex.printStackTrace(DxConsole.err);
514             } else {
515                 DxConsole.err.println("\ntrouble writing output: " +
516                                    ex.getMessage());
517             }
518             return false;
519         }
520 
521         return true;
522     }
523 
524     /**
525      * Creates and returns the manifest to use for the output. This may
526      * modify {@link #outputResources} (removing the pre-existing manifest).
527      *
528      * @return {@code non-null;} the manifest
529      */
makeManifest()530     private static Manifest makeManifest() throws IOException {
531         byte[] manifestBytes = outputResources.get(MANIFEST_NAME);
532         Manifest manifest;
533         Attributes attribs;
534 
535         if (manifestBytes == null) {
536             // We need to construct an entirely new manifest.
537             manifest = new Manifest();
538             attribs = manifest.getMainAttributes();
539             attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
540         } else {
541             manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
542             attribs = manifest.getMainAttributes();
543             outputResources.remove(MANIFEST_NAME);
544         }
545 
546         String createdBy = attribs.getValue(CREATED_BY);
547         if (createdBy == null) {
548             createdBy = "";
549         } else {
550             createdBy += " + ";
551         }
552         createdBy += "dx " + Version.VERSION;
553 
554         attribs.put(CREATED_BY, createdBy);
555         attribs.putValue("Dex-Location", DEX_IN_JAR_NAME);
556 
557         return manifest;
558     }
559 
560     /**
561      * Opens and returns the named file for writing, treating "-" specially.
562      *
563      * @param name {@code non-null;} the file name
564      * @return {@code non-null;} the opened file
565      */
openOutput(String name)566     private static OutputStream openOutput(String name) throws IOException {
567         if (name.equals("-") ||
568                 name.startsWith("-.")) {
569             return System.out;
570         }
571 
572         return new FileOutputStream(name);
573     }
574 
575     /**
576      * Flushes and closes the given output stream, except if it happens to be
577      * {@link System#out} in which case this method does the flush but not
578      * the close. This method will also silently do nothing if given a
579      * {@code null} argument.
580      *
581      * @param stream {@code null-ok;} what to close
582      */
closeOutput(OutputStream stream)583     private static void closeOutput(OutputStream stream) throws IOException {
584         if (stream == null) {
585             return;
586         }
587 
588         stream.flush();
589 
590         if (stream != System.out) {
591             stream.close();
592         }
593     }
594 
595     /**
596      * Returns the "fixed" version of a given file path, suitable for
597      * use as a path within a {@code .jar} file and for checking
598      * against a classfile-internal "this class" name. This looks for
599      * the last instance of the substring {@code "/./"} within
600      * the path, and if it finds it, it takes the portion after to be
601      * the fixed path. If that isn't found but the path starts with
602      * {@code "./"}, then that prefix is removed and the rest is
603      * return. If neither of these is the case, this method returns
604      * its argument.
605      *
606      * @param path {@code non-null;} the path to "fix"
607      * @return {@code non-null;} the fixed version (which might be the same as
608      * the given {@code path})
609      */
fixPath(String path)610     private static String fixPath(String path) {
611         /*
612          * If the path separator is \ (like on windows), we convert the
613          * path to a standard '/' separated path.
614          */
615         if (File.separatorChar == '\\') {
616             path = path.replace('\\', '/');
617         }
618 
619         int index = path.lastIndexOf("/./");
620 
621         if (index != -1) {
622             return path.substring(index + 3);
623         }
624 
625         if (path.startsWith("./")) {
626             return path.substring(2);
627         }
628 
629         return path;
630     }
631 
632     /**
633      * Dumps any method with the given name in the given file.
634      *
635      * @param dex {@code non-null;} the dex file
636      * @param fqName {@code non-null;} the fully-qualified name of the
637      * method(s)
638      * @param out {@code non-null;} where to dump to
639      */
dumpMethod(DexFile dex, String fqName, OutputStreamWriter out)640     private static void dumpMethod(DexFile dex, String fqName,
641             OutputStreamWriter out) {
642         boolean wildcard = fqName.endsWith("*");
643         int lastDot = fqName.lastIndexOf('.');
644 
645         if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) {
646             DxConsole.err.println("bogus fully-qualified method name: " +
647                                fqName);
648             return;
649         }
650 
651         String className = fqName.substring(0, lastDot).replace('.', '/');
652         String methodName = fqName.substring(lastDot + 1);
653         ClassDefItem clazz = dex.getClassOrNull(className);
654 
655         if (clazz == null) {
656             DxConsole.err.println("no such class: " + className);
657             return;
658         }
659 
660         if (wildcard) {
661             methodName = methodName.substring(0, methodName.length() - 1);
662         }
663 
664         ArrayList<EncodedMethod> allMeths = clazz.getMethods();
665         TreeMap<CstNat, EncodedMethod> meths =
666             new TreeMap<CstNat, EncodedMethod>();
667 
668         /*
669          * Figure out which methods to include in the output, and get them
670          * all sorted, so that the printout code is robust with respect to
671          * changes in the underlying order.
672          */
673         for (EncodedMethod meth : allMeths) {
674             String methName = meth.getName().getString();
675             if ((wildcard && methName.startsWith(methodName)) ||
676                 (!wildcard && methName.equals(methodName))) {
677                 meths.put(meth.getRef().getNat(), meth);
678             }
679         }
680 
681         if (meths.size() == 0) {
682             DxConsole.err.println("no such method: " + fqName);
683             return;
684         }
685 
686         PrintWriter pw = new PrintWriter(out);
687 
688         for (EncodedMethod meth : meths.values()) {
689             // TODO: Better stuff goes here, perhaps.
690             meth.debugPrint(pw, args.verboseDump);
691 
692             /*
693              * The (default) source file is an attribute of the class, but
694              * it's useful to see it in method dumps.
695              */
696             CstUtf8 sourceFile = clazz.getSourceFile();
697             if (sourceFile != null) {
698                 pw.println("  source file: " + sourceFile.toQuoted());
699             }
700 
701             Annotations methodAnnotations =
702                 clazz.getMethodAnnotations(meth.getRef());
703             AnnotationsList parameterAnnotations =
704                 clazz.getParameterAnnotations(meth.getRef());
705 
706             if (methodAnnotations != null) {
707                 pw.println("  method annotations:");
708                 for (Annotation a : methodAnnotations.getAnnotations()) {
709                     pw.println("    " + a);
710                 }
711             }
712 
713             if (parameterAnnotations != null) {
714                 pw.println("  parameter annotations:");
715                 int sz = parameterAnnotations.size();
716                 for (int i = 0; i < sz; i++) {
717                     pw.println("    parameter " + i);
718                     Annotations annotations = parameterAnnotations.get(i);
719                     for (Annotation a : annotations.getAnnotations()) {
720                         pw.println("      " + a);
721                     }
722                 }
723             }
724         }
725 
726         pw.flush();
727     }
728 
729     /**
730      * Exception class used to halt processing prematurely.
731      */
732     private static class StopProcessing extends RuntimeException {
733         // This space intentionally left blank.
734     }
735 
736     /**
737      * Command-line argument parser and access.
738      */
739     public static class Arguments {
740         /** whether to run in debug mode */
741         public boolean debug = false;
742 
743         /** whether to emit high-level verbose human-oriented output */
744         public boolean verbose = false;
745 
746         /** whether to emit verbose human-oriented output in the dump file */
747         public boolean verboseDump = false;
748 
749         /** whether we are constructing a core library */
750         public boolean coreLibrary = false;
751 
752         /** {@code null-ok;} particular method to dump */
753         public String methodToDump = null;
754 
755         /** max width for columnar output */
756         public int dumpWidth = 0;
757 
758         /** {@code null-ok;} output file name for binary file */
759         public String outName = null;
760 
761         /** {@code null-ok;} output file name for human-oriented dump */
762         public String humanOutName = null;
763 
764         /** whether strict file-name-vs-class-name checking should be done */
765         public boolean strictNameCheck = true;
766 
767         /**
768          * whether it is okay for there to be no {@code .class} files
769          * to process
770          */
771         public boolean emptyOk = false;
772 
773         /**
774          * whether the binary output is to be a {@code .jar} file
775          * instead of a plain {@code .dex}
776          */
777         public boolean jarOutput = false;
778 
779         /**
780          * when writing a {@code .jar} file, whether to still
781          * keep the {@code .class} files
782          */
783         public boolean keepClassesInJar = false;
784 
785         /** how much source position info to preserve */
786         public int positionInfo = PositionList.LINES;
787 
788         /** whether to keep local variable information */
789         public boolean localInfo = true;
790 
791         /** {@code non-null after {@link #parse};} file name arguments */
792         public String[] fileNames;
793 
794         /** whether to do SSA/register optimization */
795         public boolean optimize = true;
796 
797         /** Filename containg list of methods to optimize */
798         public String optimizeListFile = null;
799 
800         /** Filename containing list of methods to NOT optimize */
801         public String dontOptimizeListFile = null;
802 
803         /** Whether to print statistics to stdout at end of compile cycle */
804         public boolean statistics;
805 
806         /** Options for dex.cf.* */
807         public CfOptions cfOptions;
808 
809         /**
810          * Parses the given command-line arguments.
811          *
812          * @param args {@code non-null;} the arguments
813          */
parse(String[] args)814         public void parse(String[] args) {
815             int at = 0;
816 
817             for (/*at*/; at < args.length; at++) {
818                 String arg = args[at];
819                 if (arg.equals("--") || !arg.startsWith("--")) {
820                     break;
821                 } else if (arg.equals("--debug")) {
822                     debug = true;
823                 } else if (arg.equals("--verbose")) {
824                     verbose = true;
825                 } else if (arg.equals("--verbose-dump")) {
826                     verboseDump = true;
827                 } else if (arg.equals("--no-files")) {
828                     emptyOk = true;
829                 } else if (arg.equals("--no-optimize")) {
830                     optimize = false;
831                 } else if (arg.equals("--no-strict")) {
832                     strictNameCheck = false;
833                 } else if (arg.equals("--core-library")) {
834                     coreLibrary = true;
835                 } else if (arg.equals("--statistics")) {
836                     statistics = true;
837                 } else if (arg.startsWith("--optimize-list=")) {
838                     if (dontOptimizeListFile != null) {
839                         System.err.println("--optimize-list and "
840                                 + "--no-optimize-list are incompatible.");
841                         throw new UsageException();
842                     }
843                     optimize = true;
844                     optimizeListFile = arg.substring(arg.indexOf('=') + 1);
845                 } else if (arg.startsWith("--no-optimize-list=")) {
846                     if (dontOptimizeListFile != null) {
847                         System.err.println("--optimize-list and "
848                                 + "--no-optimize-list are incompatible.");
849                         throw new UsageException();
850                     }
851                     optimize = true;
852                     dontOptimizeListFile = arg.substring(arg.indexOf('=') + 1);
853                 } else if (arg.equals("--keep-classes")) {
854                     keepClassesInJar = true;
855                 } else if (arg.startsWith("--output=")) {
856                     outName = arg.substring(arg.indexOf('=') + 1);
857                     if (outName.endsWith(".zip") ||
858                             outName.endsWith(".jar") ||
859                             outName.endsWith(".apk")) {
860                         jarOutput = true;
861                     } else if (outName.endsWith(".dex") ||
862                                outName.equals("-")) {
863                         jarOutput = false;
864                     } else {
865                         System.err.println("unknown output extension: " +
866                                            outName);
867                         throw new UsageException();
868                     }
869                 } else if (arg.startsWith("--dump-to=")) {
870                     humanOutName = arg.substring(arg.indexOf('=') + 1);
871                 } else if (arg.startsWith("--dump-width=")) {
872                     arg = arg.substring(arg.indexOf('=') + 1);
873                     dumpWidth = Integer.parseInt(arg);
874                 } else if (arg.startsWith("--dump-method=")) {
875                     methodToDump = arg.substring(arg.indexOf('=') + 1);
876                     jarOutput = false;
877                 } else if (arg.startsWith("--positions=")) {
878                     String pstr = arg.substring(arg.indexOf('=') + 1).intern();
879                     if (pstr == "none") {
880                         positionInfo = PositionList.NONE;
881                     } else if (pstr == "important") {
882                         positionInfo = PositionList.IMPORTANT;
883                     } else if (pstr == "lines") {
884                         positionInfo = PositionList.LINES;
885                     } else {
886                         System.err.println("unknown positions option: " +
887                                            pstr);
888                         throw new UsageException();
889                     }
890                 } else if (arg.equals("--no-locals")) {
891                     localInfo = false;
892                 } else {
893                     System.err.println("unknown option: " + arg);
894                     throw new UsageException();
895                 }
896             }
897 
898             int fileCount = args.length - at;
899 
900             if (fileCount == 0) {
901                 if (!emptyOk) {
902                     System.err.println("no input files specified");
903                     throw new UsageException();
904                 }
905             } else if (emptyOk) {
906                 System.out.println("ignoring input files");
907                 at = 0;
908                 fileCount = 0;
909             }
910 
911             fileNames = new String[fileCount];
912             System.arraycopy(args, at, fileNames, 0, fileCount);
913 
914             if ((humanOutName == null) && (methodToDump != null)) {
915                 humanOutName = "-";
916             }
917 
918             makeCfOptions();
919         }
920 
921         /**
922          * Copies relevent arguments over into a CfOptions instance.
923          */
makeCfOptions()924         private void makeCfOptions() {
925             cfOptions = new CfOptions();
926 
927             cfOptions.positionInfo = positionInfo;
928             cfOptions.localInfo = localInfo;
929             cfOptions.strictNameCheck = strictNameCheck;
930             cfOptions.optimize = optimize;
931             cfOptions.optimizeListFile = optimizeListFile;
932             cfOptions.dontOptimizeListFile = dontOptimizeListFile;
933             cfOptions.statistics = statistics;
934             cfOptions.warn = DxConsole.err;
935         }
936     }
937 }
938