1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2009 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.obfuscate;
22
23import proguard.*;
24import proguard.classfile.*;
25import proguard.classfile.attribute.visitor.*;
26import proguard.classfile.constant.visitor.AllConstantVisitor;
27import proguard.classfile.editor.*;
28import proguard.classfile.util.*;
29import proguard.classfile.visitor.*;
30import proguard.util.*;
31
32import java.io.*;
33import java.util.*;
34
35/**
36 * This class can perform obfuscation of class pools according to a given
37 * specification.
38 *
39 * @author Eric Lafortune
40 */
41public class Obfuscator
42{
43    private final Configuration configuration;
44
45
46    /**
47     * Creates a new Obfuscator.
48     */
49    public Obfuscator(Configuration configuration)
50    {
51        this.configuration = configuration;
52    }
53
54
55    /**
56     * Performs obfuscation of the given program class pool.
57     */
58    public void execute(ClassPool programClassPool,
59                        ClassPool libraryClassPool) throws IOException
60    {
61        // Check if we have at least some keep commands.
62        if (configuration.keep         == null &&
63            configuration.applyMapping == null &&
64            configuration.printMapping == null)
65        {
66            throw new IOException("You have to specify '-keep' options for the obfuscation step.");
67        }
68
69        // Clean up any old visitor info.
70        programClassPool.classesAccept(new ClassCleaner());
71        libraryClassPool.classesAccept(new ClassCleaner());
72
73        // If the class member names have to correspond globally,
74        // link all class members in all classes, otherwise
75        // link all non-private methods in all class hierarchies.
76        ClassVisitor memberInfoLinker =
77            configuration.useUniqueClassMemberNames ?
78                (ClassVisitor)new AllMemberVisitor(new MethodLinker()) :
79                (ClassVisitor)new BottomClassFilter(new MethodLinker());
80
81        programClassPool.classesAccept(memberInfoLinker);
82        libraryClassPool.classesAccept(memberInfoLinker);
83
84        // Create a visitor for marking the seeds.
85        NameMarker nameMarker = new NameMarker();
86        ClassPoolVisitor classPoolvisitor =
87            ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
88                                                                    nameMarker,
89                                                                    nameMarker,
90                                                                    false,
91                                                                    false,
92                                                                    true);
93        // Mark the seeds.
94        programClassPool.accept(classPoolvisitor);
95        libraryClassPool.accept(classPoolvisitor);
96
97        // All library classes and library class members keep their names.
98        libraryClassPool.classesAccept(nameMarker);
99        libraryClassPool.classesAccept(new AllMemberVisitor(nameMarker));
100
101        // Mark attributes that have to be kept.
102        AttributeUsageMarker requiredAttributeUsageMarker =
103            new AttributeUsageMarker();
104
105        AttributeVisitor optionalAttributeUsageMarker =
106            configuration.keepAttributes == null ? null :
107                new AttributeNameFilter(new ListParser(new NameParser()).parse(configuration.keepAttributes),
108                                        requiredAttributeUsageMarker);
109
110        programClassPool.classesAccept(
111            new AllAttributeVisitor(true,
112            new RequiredAttributeFilter(requiredAttributeUsageMarker,
113                                        optionalAttributeUsageMarker)));
114
115        // Remove the attributes that can be discarded. Note that the attributes
116        // may only be discarded after the seeds have been marked, since the
117        // configuration may rely on annotations.
118        programClassPool.classesAccept(new AttributeShrinker());
119
120        // Apply the mapping, if one has been specified. The mapping can
121        // override the names of library classes and of library class members.
122        if (configuration.applyMapping != null)
123        {
124            WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
125
126            MappingReader reader = new MappingReader(configuration.applyMapping);
127
128            MappingProcessor keeper =
129                new MultiMappingProcessor(new MappingProcessor[]
130                {
131                    new MappingKeeper(programClassPool, warningPrinter),
132                    new MappingKeeper(libraryClassPool, null),
133                });
134
135            reader.pump(keeper);
136
137            // Print out a summary of the warnings if necessary.
138            int mappingWarningCount = warningPrinter.getWarningCount();
139            if (mappingWarningCount > 0)
140            {
141                System.err.println("Warning: there were " + mappingWarningCount +
142                                                            " kept classes and class members that were remapped anyway.");
143                System.err.println("         You should adapt your configuration or edit the mapping file.");
144
145                if (!configuration.ignoreWarnings)
146                {
147                    System.err.println("         If you are sure this remapping won't hurt,");
148                    System.err.println("         you could try your luck using the '-ignorewarnings' option.");
149                    throw new IOException("Please correct the above warnings first.");
150                }
151            }
152        }
153
154        // Come up with new names for all classes.
155        DictionaryNameFactory classNameFactory = configuration.classObfuscationDictionary != null ?
156            new DictionaryNameFactory(configuration.classObfuscationDictionary, null) :
157            null;
158
159        DictionaryNameFactory packageNameFactory = configuration.packageObfuscationDictionary != null ?
160            new DictionaryNameFactory(configuration.packageObfuscationDictionary, null) :
161            null;
162
163        programClassPool.classesAccept(
164            new ClassObfuscator(programClassPool,
165                                classNameFactory,
166                                packageNameFactory,
167                                configuration.useMixedCaseClassNames,
168                                configuration.keepPackageNames,
169                                configuration.flattenPackageHierarchy,
170                                configuration.repackageClasses,
171                                configuration.allowAccessModification));
172
173        // Come up with new names for all class members.
174        NameFactory nameFactory = new SimpleNameFactory();
175
176        if (configuration.obfuscationDictionary != null)
177        {
178            nameFactory = new DictionaryNameFactory(configuration.obfuscationDictionary,
179                                                    nameFactory);
180        }
181
182        WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
183
184        // Maintain a map of names to avoid [descriptor - new name - old name].
185        Map descriptorMap = new HashMap();
186
187        // Do the class member names have to be globally unique?
188        if (configuration.useUniqueClassMemberNames)
189        {
190            // Collect all member names in all classes.
191            programClassPool.classesAccept(
192                new AllMemberVisitor(
193                new MemberNameCollector(configuration.overloadAggressively,
194                                        descriptorMap)));
195
196            // Assign new names to all members in all classes.
197            programClassPool.classesAccept(
198                new AllMemberVisitor(
199                new MemberObfuscator(configuration.overloadAggressively,
200                                     nameFactory,
201                                     descriptorMap)));
202        }
203        else
204        {
205            // Come up with new names for all non-private class members.
206            programClassPool.classesAccept(
207                new MultiClassVisitor(new ClassVisitor[]
208                {
209                    // Collect all private member names in this class and down
210                    // the hierarchy.
211                    new ClassHierarchyTraveler(true, false, false, true,
212                    new AllMemberVisitor(
213                    new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
214                    new MemberNameCollector(configuration.overloadAggressively,
215                                            descriptorMap)))),
216
217                    // Collect all non-private member names anywhere in the hierarchy.
218                    new ClassHierarchyTraveler(true, true, true, true,
219                    new AllMemberVisitor(
220                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
221                    new MemberNameCollector(configuration.overloadAggressively,
222                                            descriptorMap)))),
223
224                    // Assign new names to all non-private members in this class.
225                    new AllMemberVisitor(
226                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
227                    new MemberObfuscator(configuration.overloadAggressively,
228                                         nameFactory,
229                                         descriptorMap))),
230
231                    // Clear the collected names.
232                    new MapCleaner(descriptorMap)
233                }));
234
235            // Come up with new names for all private class members.
236            programClassPool.classesAccept(
237                new MultiClassVisitor(new ClassVisitor[]
238                {
239                    // Collect all member names in this class.
240                    new AllMemberVisitor(
241                    new MemberNameCollector(configuration.overloadAggressively,
242                                            descriptorMap)),
243
244                    // Collect all non-private member names higher up the hierarchy.
245                    new ClassHierarchyTraveler(false, true, true, false,
246                    new AllMemberVisitor(
247                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
248                    new MemberNameCollector(configuration.overloadAggressively,
249                                            descriptorMap)))),
250
251                    // Assign new names to all private members in this class.
252                    new AllMemberVisitor(
253                    new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
254                    new MemberObfuscator(configuration.overloadAggressively,
255                                         nameFactory,
256                                         descriptorMap))),
257
258                    // Clear the collected names.
259                    new MapCleaner(descriptorMap)
260                }));
261        }
262
263        // Some class members may have ended up with conflicting names.
264        // Come up with new, globally unique names for them.
265        NameFactory specialNameFactory =
266            new SpecialNameFactory(new SimpleNameFactory());
267
268        // Collect a map of special names to avoid
269        // [descriptor - new name - old name].
270        Map specialDescriptorMap = new HashMap();
271
272        programClassPool.classesAccept(
273            new AllMemberVisitor(
274            new MemberSpecialNameFilter(
275            new MemberNameCollector(configuration.overloadAggressively,
276                                    specialDescriptorMap))));
277
278        libraryClassPool.classesAccept(
279            new AllMemberVisitor(
280            new MemberSpecialNameFilter(
281            new MemberNameCollector(configuration.overloadAggressively,
282                                    specialDescriptorMap))));
283
284        // Replace conflicting non-private member names with special names.
285        programClassPool.classesAccept(
286            new MultiClassVisitor(new ClassVisitor[]
287            {
288                // Collect all private member names in this class and down
289                // the hierarchy.
290                new ClassHierarchyTraveler(true, false, false, true,
291                new AllMemberVisitor(
292                new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
293                new MemberNameCollector(configuration.overloadAggressively,
294                                        descriptorMap)))),
295
296                // Collect all non-private member names in this class and
297                // higher up the hierarchy.
298                new ClassHierarchyTraveler(true, true, true, false,
299                new AllMemberVisitor(
300                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
301                new MemberNameCollector(configuration.overloadAggressively,
302                                        descriptorMap)))),
303
304                // Assign new names to all conflicting non-private members
305                // in this class and higher up the hierarchy.
306                new ClassHierarchyTraveler(true, true, true, false,
307                new AllMemberVisitor(
308                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
309                new MemberNameConflictFixer(configuration.overloadAggressively,
310                                            descriptorMap,
311                                            warningPrinter,
312                new MemberObfuscator(configuration.overloadAggressively,
313                                     specialNameFactory,
314                                     specialDescriptorMap))))),
315
316                // Clear the collected names.
317                new MapCleaner(descriptorMap)
318            }));
319
320        // Replace conflicting private member names with special names.
321        // This is only possible if those names were kept or mapped.
322        programClassPool.classesAccept(
323            new MultiClassVisitor(new ClassVisitor[]
324            {
325                // Collect all member names in this class.
326                new AllMemberVisitor(
327                new MemberNameCollector(configuration.overloadAggressively,
328                                        descriptorMap)),
329
330                // Collect all non-private member names higher up the hierarchy.
331                new ClassHierarchyTraveler(false, true, true, false,
332                new AllMemberVisitor(
333                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
334                new MemberNameCollector(configuration.overloadAggressively,
335                                        descriptorMap)))),
336
337                // Assign new names to all conflicting private members in this
338                // class.
339                new AllMemberVisitor(
340                new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
341                new MemberNameConflictFixer(configuration.overloadAggressively,
342                                            descriptorMap,
343                                            warningPrinter,
344                new MemberObfuscator(configuration.overloadAggressively,
345                                     specialNameFactory,
346                                     specialDescriptorMap)))),
347
348                // Clear the collected names.
349                new MapCleaner(descriptorMap)
350            }));
351
352        // Print out any warnings about member name conflicts.
353        int warningCount = warningPrinter.getWarningCount();
354        if (warningCount > 0)
355        {
356            System.err.println("Warning: there were " + warningCount +
357                               " conflicting class member name mappings.");
358            System.err.println("         Your configuration may be inconsistent.");
359
360            if (!configuration.ignoreWarnings)
361            {
362                System.err.println("         If you are sure the conflicts are harmless,");
363                System.err.println("         you could try your luck using the '-ignorewarnings' option.");
364                throw new IOException("Please correct the above warnings first.");
365            }
366        }
367
368        // Print out the mapping, if requested.
369        if (configuration.printMapping != null)
370        {
371            PrintStream ps = isFile(configuration.printMapping) ?
372                new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printMapping))) :
373                System.out;
374
375            // Print out items that will be removed.
376            programClassPool.classesAcceptAlphabetically(new MappingPrinter(ps));
377
378            if (ps != System.out)
379            {
380                ps.close();
381            }
382        }
383
384        // Actually apply the new names.
385        programClassPool.classesAccept(new ClassRenamer());
386        libraryClassPool.classesAccept(new ClassRenamer());
387
388        // Update all references to these new names.
389        programClassPool.classesAccept(new ClassReferenceFixer(false));
390        libraryClassPool.classesAccept(new ClassReferenceFixer(false));
391        programClassPool.classesAccept(new MemberReferenceFixer());
392
393        // Make package visible elements public or protected, if obfuscated
394        // classes are being repackaged aggressively.
395        if (configuration.repackageClasses != null &&
396            configuration.allowAccessModification)
397        {
398            programClassPool.classesAccept(
399                new AllConstantVisitor(
400                new AccessFixer()));
401        }
402
403        // Rename the source file attributes, if requested.
404        if (configuration.newSourceFileAttribute != null)
405        {
406            programClassPool.classesAccept(new SourceFileRenamer(configuration.newSourceFileAttribute));
407        }
408
409        // Mark NameAndType constant pool entries that have to be kept
410        // and remove the other ones.
411        programClassPool.classesAccept(new NameAndTypeUsageMarker());
412        programClassPool.classesAccept(new NameAndTypeShrinker());
413
414        // Mark Utf8 constant pool entries that have to be kept
415        // and remove the other ones.
416        programClassPool.classesAccept(new Utf8UsageMarker());
417        programClassPool.classesAccept(new Utf8Shrinker());
418    }
419
420
421    /**
422     * Returns whether the given file is actually a file, or just a placeholder
423     * for the standard output.
424     */
425    private boolean isFile(File file)
426    {
427        return file.getPath().length() > 0;
428    }
429}
430