1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.visitor.AllAttributeVisitor;
25import proguard.classfile.editor.*;
26import proguard.classfile.visitor.*;
27import proguard.obfuscate.Obfuscator;
28import proguard.optimize.Optimizer;
29import proguard.preverify.*;
30import proguard.shrink.Shrinker;
31
32import java.io.*;
33
34/**
35 * Tool for shrinking, optimizing, obfuscating, and preverifying Java classes.
36 *
37 * @author Eric Lafortune
38 */
39public class ProGuard
40{
41    public static final String VERSION = "ProGuard, version 5.1";
42
43    private final Configuration configuration;
44    private       ClassPool     programClassPool = new ClassPool();
45    private final ClassPool     libraryClassPool = new ClassPool();
46
47
48    /**
49     * Creates a new ProGuard object to process jars as specified by the given
50     * configuration.
51     */
52    public ProGuard(Configuration configuration)
53    {
54        this.configuration = configuration;
55    }
56
57
58    /**
59     * Performs all subsequent ProGuard operations.
60     */
61    public void execute() throws IOException
62    {
63        System.out.println(VERSION);
64
65        GPL.check();
66
67        if (configuration.printConfiguration != null)
68        {
69            printConfiguration();
70        }
71
72        new ConfigurationChecker(configuration).check();
73
74        if (configuration.programJars != null     &&
75            configuration.programJars.hasOutput() &&
76            new UpToDateChecker(configuration).check())
77        {
78            return;
79        }
80
81        readInput();
82
83        if (configuration.shrink    ||
84            configuration.optimize  ||
85            configuration.obfuscate ||
86            configuration.preverify)
87        {
88            clearPreverification();
89        }
90
91        if (configuration.printSeeds != null ||
92            configuration.shrink    ||
93            configuration.optimize  ||
94            configuration.obfuscate ||
95            configuration.preverify)
96        {
97            initialize();
98        }
99
100        if (configuration.targetClassVersion != 0)
101        {
102            target();
103        }
104
105        if (configuration.printSeeds != null)
106        {
107            printSeeds();
108        }
109
110        if (configuration.shrink)
111        {
112            shrink();
113        }
114
115        if (configuration.preverify)
116        {
117            inlineSubroutines();
118        }
119
120        if (configuration.optimize)
121        {
122            for (int optimizationPass = 0;
123                 optimizationPass < configuration.optimizationPasses;
124                 optimizationPass++)
125            {
126                if (!optimize())
127                {
128                    // Stop optimizing if the code doesn't improve any further.
129                    break;
130                }
131
132                // Shrink again, if we may.
133                if (configuration.shrink)
134                {
135                    // Don't print any usage this time around.
136                    configuration.printUsage       = null;
137                    configuration.whyAreYouKeeping = null;
138
139                    shrink();
140                }
141            }
142        }
143
144        if (configuration.obfuscate)
145        {
146            obfuscate();
147        }
148
149        if (configuration.preverify)
150        {
151            preverify();
152        }
153
154        if (configuration.shrink    ||
155            configuration.optimize  ||
156            configuration.obfuscate ||
157            configuration.preverify)
158        {
159            sortClassElements();
160        }
161
162        if (configuration.programJars.hasOutput())
163        {
164            writeOutput();
165        }
166
167        if (configuration.dump != null)
168        {
169            dump();
170        }
171    }
172
173
174    /**
175     * Prints out the configuration that ProGuard is using.
176     */
177    private void printConfiguration() throws IOException
178    {
179        if (configuration.verbose)
180        {
181            System.out.println("Printing configuration to [" + fileName(configuration.printConfiguration) + "]...");
182        }
183
184        PrintStream ps = createPrintStream(configuration.printConfiguration);
185        try
186        {
187            new ConfigurationWriter(ps).write(configuration);
188        }
189        finally
190        {
191            closePrintStream(ps);
192        }
193    }
194
195
196    /**
197     * Reads the input class files.
198     */
199    private void readInput() throws IOException
200    {
201        if (configuration.verbose)
202        {
203            System.out.println("Reading input...");
204        }
205
206        // Fill the program class pool and the library class pool.
207        new InputReader(configuration).execute(programClassPool, libraryClassPool);
208    }
209
210
211    /**
212     * Initializes the cross-references between all classes, performs some
213     * basic checks, and shrinks the library class pool.
214     */
215    private void initialize() throws IOException
216    {
217        if (configuration.verbose)
218        {
219            System.out.println("Initializing...");
220        }
221
222        new Initializer(configuration).execute(programClassPool, libraryClassPool);
223    }
224
225
226    /**
227     * Sets that target versions of the program classes.
228     */
229    private void target() throws IOException
230    {
231        if (configuration.verbose)
232        {
233            System.out.println("Setting target versions...");
234        }
235
236        new Targeter(configuration).execute(programClassPool);
237    }
238
239
240    /**
241     * Prints out classes and class members that are used as seeds in the
242     * shrinking and obfuscation steps.
243     */
244    private void printSeeds() throws IOException
245    {
246        if (configuration.verbose)
247        {
248            System.out.println("Printing kept classes, fields, and methods...");
249        }
250
251        PrintStream ps = createPrintStream(configuration.printSeeds);
252        try
253        {
254            new SeedPrinter(ps).write(configuration, programClassPool, libraryClassPool);
255        }
256        finally
257        {
258            closePrintStream(ps);
259        }
260    }
261
262
263    /**
264     * Performs the shrinking step.
265     */
266    private void shrink() throws IOException
267    {
268        if (configuration.verbose)
269        {
270            System.out.println("Shrinking...");
271
272            // We'll print out some explanation, if requested.
273            if (configuration.whyAreYouKeeping != null)
274            {
275                System.out.println("Explaining why classes and class members are being kept...");
276            }
277
278            // We'll print out the usage, if requested.
279            if (configuration.printUsage != null)
280            {
281                System.out.println("Printing usage to [" + fileName(configuration.printUsage) + "]...");
282            }
283        }
284
285        // Perform the actual shrinking.
286        programClassPool =
287            new Shrinker(configuration).execute(programClassPool, libraryClassPool);
288    }
289
290
291    /**
292     * Performs the subroutine inlining step.
293     */
294    private void inlineSubroutines()
295    {
296        if (configuration.verbose)
297        {
298            System.out.println("Inlining subroutines...");
299        }
300
301        // Perform the actual inlining.
302        new SubroutineInliner(configuration).execute(programClassPool);
303    }
304
305
306    /**
307     * Performs the optimization step.
308     */
309    private boolean optimize() throws IOException
310    {
311        if (configuration.verbose)
312        {
313            System.out.println("Optimizing...");
314        }
315
316        // Perform the actual optimization.
317        return new Optimizer(configuration).execute(programClassPool, libraryClassPool);
318    }
319
320
321    /**
322     * Performs the obfuscation step.
323     */
324    private void obfuscate() throws IOException
325    {
326        if (configuration.verbose)
327        {
328            System.out.println("Obfuscating...");
329
330            // We'll apply a mapping, if requested.
331            if (configuration.applyMapping != null)
332            {
333                System.out.println("Applying mapping [" + fileName(configuration.applyMapping) + "]");
334            }
335
336            // We'll print out the mapping, if requested.
337            if (configuration.printMapping != null)
338            {
339                System.out.println("Printing mapping to [" + fileName(configuration.printMapping) + "]...");
340            }
341        }
342
343        // Perform the actual obfuscation.
344        new Obfuscator(configuration).execute(programClassPool, libraryClassPool);
345    }
346
347
348    /**
349     * Clears any JSE preverification information from the program classes.
350     */
351    private void clearPreverification()
352    {
353        programClassPool.classesAccept(
354            new ClassVersionFilter(ClassConstants.CLASS_VERSION_1_6,
355            new AllMethodVisitor(
356            new AllAttributeVisitor(
357            new NamedAttributeDeleter(ClassConstants.ATTR_StackMapTable)))));
358    }
359
360
361    /**
362     * Performs the preverification step.
363     */
364    private void preverify()
365    {
366        if (configuration.verbose)
367        {
368            System.out.println("Preverifying...");
369        }
370
371        // Perform the actual preverification.
372        new Preverifier(configuration).execute(programClassPool);
373    }
374
375
376    /**
377     * Sorts the elements of all program classes.
378     */
379    private void sortClassElements()
380    {
381        programClassPool.classesAccept(new ClassElementSorter());
382    }
383
384
385    /**
386     * Writes the output class files.
387     */
388    private void writeOutput() throws IOException
389    {
390        if (configuration.verbose)
391        {
392            System.out.println("Writing output...");
393        }
394
395        // Write out the program class pool.
396        new OutputWriter(configuration).execute(programClassPool);
397    }
398
399
400    /**
401     * Prints out the contents of the program classes.
402     */
403    private void dump() throws IOException
404    {
405        if (configuration.verbose)
406        {
407            System.out.println("Printing classes to [" + fileName(configuration.dump) + "]...");
408        }
409
410        PrintStream ps = createPrintStream(configuration.dump);
411        try
412        {
413            programClassPool.classesAccept(new ClassPrinter(ps));
414        }
415        finally
416        {
417            closePrintStream(ps);
418        }
419    }
420
421
422    /**
423     * Returns a print stream for the given file, or the standard output if
424     * the file name is empty.
425     */
426    private PrintStream createPrintStream(File file)
427    throws FileNotFoundException
428    {
429        return file == Configuration.STD_OUT ? System.out :
430            new PrintStream(
431            new BufferedOutputStream(
432            new FileOutputStream(file)));
433    }
434
435
436    /**
437     * Closes the given print stream, or closes it if is the standard output.
438     * @param printStream
439     */
440    private void closePrintStream(PrintStream printStream)
441    {
442        if (printStream == System.out)
443        {
444            printStream.flush();
445        }
446        else
447        {
448            printStream.close();
449        }
450    }
451
452
453    /**
454     * Returns the canonical file name for the given file, or "standard output"
455     * if the file name is empty.
456     */
457    private String fileName(File file)
458    {
459        if (file == Configuration.STD_OUT)
460        {
461            return "standard output";
462        }
463        else
464        {
465            try
466            {
467                return file.getCanonicalPath();
468            }
469            catch (IOException ex)
470            {
471                return file.getPath();
472            }
473        }
474    }
475
476
477    /**
478     * The main method for ProGuard.
479     */
480    public static void main(String[] args)
481    {
482        if (args.length == 0)
483        {
484            System.out.println(VERSION);
485            System.out.println("Usage: java proguard.ProGuard [options ...]");
486            System.exit(1);
487        }
488
489        // Create the default options.
490        Configuration configuration = new Configuration();
491
492        try
493        {
494            // Parse the options specified in the command line arguments.
495            ConfigurationParser parser = new ConfigurationParser(args,
496                                                                 System.getProperties());
497            try
498            {
499                parser.parse(configuration);
500            }
501            finally
502            {
503                parser.close();
504            }
505
506            // Execute ProGuard with these options.
507            new ProGuard(configuration).execute();
508        }
509        catch (Exception ex)
510        {
511            if (configuration.verbose)
512            {
513                // Print a verbose stack trace.
514                ex.printStackTrace();
515            }
516            else
517            {
518                // Print just the stack trace message.
519                System.err.println("Error: "+ex.getMessage());
520            }
521
522            System.exit(1);
523        }
524
525        System.exit(0);
526    }
527}
528