1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2013 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        AttributeVisitor attributeUsageMarker =
103            new NonEmptyAttributeFilter(
104            new AttributeUsageMarker());
105
106        AttributeVisitor optionalAttributeUsageMarker =
107            configuration.keepAttributes == null ? null :
108                new AttributeNameFilter(new ListParser(new NameParser()).parse(configuration.keepAttributes),
109                                        attributeUsageMarker);
110
111        programClassPool.classesAccept(
112            new AllAttributeVisitor(true,
113            new RequiredAttributeFilter(attributeUsageMarker,
114                                        optionalAttributeUsageMarker)));
115
116        // Keep parameter names and types if specified.
117        if (configuration.keepParameterNames)
118        {
119            programClassPool.classesAccept(
120                new AllMethodVisitor(
121                new MemberNameFilter(
122                new AllAttributeVisitor(true,
123                new ParameterNameMarker(attributeUsageMarker)))));
124        }
125
126        // Remove the attributes that can be discarded. Note that the attributes
127        // may only be discarded after the seeds have been marked, since the
128        // configuration may rely on annotations.
129        programClassPool.classesAccept(new AttributeShrinker());
130
131        // Apply the mapping, if one has been specified. The mapping can
132        // override the names of library classes and of library class members.
133        if (configuration.applyMapping != null)
134        {
135            WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
136
137            MappingReader reader = new MappingReader(configuration.applyMapping);
138
139            MappingProcessor keeper =
140                new MultiMappingProcessor(new MappingProcessor[]
141                {
142                    new MappingKeeper(programClassPool, warningPrinter),
143                    new MappingKeeper(libraryClassPool, null),
144                });
145
146            reader.pump(keeper);
147
148            // Print out a summary of the warnings if necessary.
149            int warningCount = warningPrinter.getWarningCount();
150            if (warningCount > 0)
151            {
152                System.err.println("Warning: there were " + warningCount +
153                                   " kept classes and class members that were remapped anyway.");
154                System.err.println("         You should adapt your configuration or edit the mapping file.");
155
156                if (!configuration.ignoreWarnings)
157                {
158                    System.err.println("         If you are sure this remapping won't hurt, you could try your luck");
159                    System.err.println("         using the '-ignorewarnings' option.");
160                }
161
162                System.err.println("         (http://proguard.sourceforge.net/manual/troubleshooting.html#mappingconflict1)");
163
164                if (!configuration.ignoreWarnings)
165                {
166                    throw new IOException("Please correct the above warnings first.");
167                }
168            }
169        }
170
171        // Come up with new names for all classes.
172        DictionaryNameFactory classNameFactory = configuration.classObfuscationDictionary != null ?
173            new DictionaryNameFactory(configuration.classObfuscationDictionary, null) :
174            null;
175
176        DictionaryNameFactory packageNameFactory = configuration.packageObfuscationDictionary != null ?
177            new DictionaryNameFactory(configuration.packageObfuscationDictionary, null) :
178            null;
179
180        programClassPool.classesAccept(
181            new ClassObfuscator(programClassPool,
182                                classNameFactory,
183                                packageNameFactory,
184                                configuration.useMixedCaseClassNames,
185                                configuration.keepPackageNames,
186                                configuration.flattenPackageHierarchy,
187                                configuration.repackageClasses,
188                                configuration.allowAccessModification));
189
190        // Come up with new names for all class members.
191        NameFactory nameFactory = new SimpleNameFactory();
192
193        if (configuration.obfuscationDictionary != null)
194        {
195            nameFactory = new DictionaryNameFactory(configuration.obfuscationDictionary,
196                                                    nameFactory);
197        }
198
199        WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
200
201        // Maintain a map of names to avoid [descriptor - new name - old name].
202        Map descriptorMap = new HashMap();
203
204        // Do the class member names have to be globally unique?
205        if (configuration.useUniqueClassMemberNames)
206        {
207            // Collect all member names in all classes.
208            programClassPool.classesAccept(
209                new AllMemberVisitor(
210                new MemberNameCollector(configuration.overloadAggressively,
211                                        descriptorMap)));
212
213            // Assign new names to all members in all classes.
214            programClassPool.classesAccept(
215                new AllMemberVisitor(
216                new MemberObfuscator(configuration.overloadAggressively,
217                                     nameFactory,
218                                     descriptorMap)));
219        }
220        else
221        {
222            // Come up with new names for all non-private class members.
223            programClassPool.classesAccept(
224                new MultiClassVisitor(new ClassVisitor[]
225                {
226                    // Collect all private member names in this class and down
227                    // the hierarchy.
228                    new ClassHierarchyTraveler(true, false, false, true,
229                    new AllMemberVisitor(
230                    new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
231                    new MemberNameCollector(configuration.overloadAggressively,
232                                            descriptorMap)))),
233
234                    // Collect all non-private member names anywhere in the hierarchy.
235                    new ClassHierarchyTraveler(true, true, true, true,
236                    new AllMemberVisitor(
237                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
238                    new MemberNameCollector(configuration.overloadAggressively,
239                                            descriptorMap)))),
240
241                    // Assign new names to all non-private members in this class.
242                    new AllMemberVisitor(
243                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
244                    new MemberObfuscator(configuration.overloadAggressively,
245                                         nameFactory,
246                                         descriptorMap))),
247
248                    // Clear the collected names.
249                    new MapCleaner(descriptorMap)
250                }));
251
252            // Come up with new names for all private class members.
253            programClassPool.classesAccept(
254                new MultiClassVisitor(new ClassVisitor[]
255                {
256                    // Collect all member names in this class.
257                    new AllMemberVisitor(
258                    new MemberNameCollector(configuration.overloadAggressively,
259                                            descriptorMap)),
260
261                    // Collect all non-private member names higher up the hierarchy.
262                    new ClassHierarchyTraveler(false, true, true, false,
263                    new AllMemberVisitor(
264                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
265                    new MemberNameCollector(configuration.overloadAggressively,
266                                            descriptorMap)))),
267
268                    // Collect all member names from interfaces of abstract
269                    // classes down the hierarchy.
270                    // Due to an error in the JLS/JVMS, virtual invocations
271                    // may end up at a private method otherwise (Sun/Oracle
272                    // bugs #6691741 and #6684387, ProGuard bug #3471941,
273                    // and ProGuard test #1180).
274                    new ClassHierarchyTraveler(false, false, false, true,
275                    new ClassAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0,
276                    new ClassHierarchyTraveler(false, false, true, false,
277                    new AllMemberVisitor(
278                    new MemberNameCollector(configuration.overloadAggressively,
279                                            descriptorMap))))),
280
281                    // Assign new names to all private members in this class.
282                    new AllMemberVisitor(
283                    new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
284                    new MemberObfuscator(configuration.overloadAggressively,
285                                         nameFactory,
286                                         descriptorMap))),
287
288                    // Clear the collected names.
289                    new MapCleaner(descriptorMap)
290                }));
291        }
292
293        // Some class members may have ended up with conflicting names.
294        // Come up with new, globally unique names for them.
295        NameFactory specialNameFactory =
296            new SpecialNameFactory(new SimpleNameFactory());
297
298        // Collect a map of special names to avoid
299        // [descriptor - new name - old name].
300        Map specialDescriptorMap = new HashMap();
301
302        programClassPool.classesAccept(
303            new AllMemberVisitor(
304            new MemberSpecialNameFilter(
305            new MemberNameCollector(configuration.overloadAggressively,
306                                    specialDescriptorMap))));
307
308        libraryClassPool.classesAccept(
309            new AllMemberVisitor(
310            new MemberSpecialNameFilter(
311            new MemberNameCollector(configuration.overloadAggressively,
312                                    specialDescriptorMap))));
313
314        // Replace conflicting non-private member names with special names.
315        programClassPool.classesAccept(
316            new MultiClassVisitor(new ClassVisitor[]
317            {
318                // Collect all private member names in this class and down
319                // the hierarchy.
320                new ClassHierarchyTraveler(true, false, false, true,
321                new AllMemberVisitor(
322                new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
323                new MemberNameCollector(configuration.overloadAggressively,
324                                        descriptorMap)))),
325
326                // Collect all non-private member names in this class and
327                // higher up the hierarchy.
328                new ClassHierarchyTraveler(true, true, true, false,
329                new AllMemberVisitor(
330                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
331                new MemberNameCollector(configuration.overloadAggressively,
332                                        descriptorMap)))),
333
334                // Assign new names to all conflicting non-private members
335                // in this class and higher up the hierarchy.
336                new ClassHierarchyTraveler(true, true, true, false,
337                new AllMemberVisitor(
338                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
339                new MemberNameConflictFixer(configuration.overloadAggressively,
340                                            descriptorMap,
341                                            warningPrinter,
342                new MemberObfuscator(configuration.overloadAggressively,
343                                     specialNameFactory,
344                                     specialDescriptorMap))))),
345
346                // Clear the collected names.
347                new MapCleaner(descriptorMap)
348            }));
349
350        // Replace conflicting private member names with special names.
351        // This is only possible if those names were kept or mapped.
352        programClassPool.classesAccept(
353            new MultiClassVisitor(new ClassVisitor[]
354            {
355                // Collect all member names in this class.
356                new AllMemberVisitor(
357                new MemberNameCollector(configuration.overloadAggressively,
358                                        descriptorMap)),
359
360                // Collect all non-private member names higher up the hierarchy.
361                new ClassHierarchyTraveler(false, true, true, false,
362                new AllMemberVisitor(
363                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
364                new MemberNameCollector(configuration.overloadAggressively,
365                                        descriptorMap)))),
366
367                // Assign new names to all conflicting private members in this
368                // class.
369                new AllMemberVisitor(
370                new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
371                new MemberNameConflictFixer(configuration.overloadAggressively,
372                                            descriptorMap,
373                                            warningPrinter,
374                new MemberObfuscator(configuration.overloadAggressively,
375                                     specialNameFactory,
376                                     specialDescriptorMap)))),
377
378                // Clear the collected names.
379                new MapCleaner(descriptorMap)
380            }));
381
382        // Print out any warnings about member name conflicts.
383        int warningCount = warningPrinter.getWarningCount();
384        if (warningCount > 0)
385        {
386            System.err.println("Warning: there were " + warningCount +
387                               " conflicting class member name mappings.");
388            System.err.println("         Your configuration may be inconsistent.");
389
390            if (!configuration.ignoreWarnings)
391            {
392                System.err.println("         If you are sure the conflicts are harmless,");
393                System.err.println("         you could try your luck using the '-ignorewarnings' option.");
394                }
395
396                System.err.println("         (http://proguard.sourceforge.net/manual/troubleshooting.html#mappingconflict2)");
397
398                if (!configuration.ignoreWarnings)
399                {
400                throw new IOException("Please correct the above warnings first.");
401            }
402        }
403
404        // Print out the mapping, if requested.
405        if (configuration.printMapping != null)
406        {
407            PrintStream ps =
408                configuration.printMapping == Configuration.STD_OUT ? System.out :
409                    new PrintStream(
410                    new BufferedOutputStream(
411                    new FileOutputStream(configuration.printMapping)));
412
413            // Print out items that will be removed.
414            programClassPool.classesAcceptAlphabetically(new MappingPrinter(ps));
415
416            if (ps == System.out)
417            {
418                ps.flush();
419            }
420            else
421            {
422                ps.close();
423            }
424        }
425
426        // Actually apply the new names.
427        programClassPool.classesAccept(new ClassRenamer());
428        libraryClassPool.classesAccept(new ClassRenamer());
429
430        // Update all references to these new names.
431        programClassPool.classesAccept(new ClassReferenceFixer(false));
432        libraryClassPool.classesAccept(new ClassReferenceFixer(false));
433        programClassPool.classesAccept(new MemberReferenceFixer());
434
435        // Make package visible elements public or protected, if obfuscated
436        // classes are being repackaged aggressively.
437        if (configuration.repackageClasses != null &&
438            configuration.allowAccessModification)
439        {
440            programClassPool.classesAccept(
441                new AllConstantVisitor(
442                new AccessFixer()));
443
444            // Fix the access flags of the inner classes information.
445            programClassPool.classesAccept(
446                new AllAttributeVisitor(
447                new AllInnerClassesInfoVisitor(
448                new InnerClassesAccessFixer())));
449        }
450
451        // Fix the bridge method flags.
452        programClassPool.classesAccept(
453            new AllMethodVisitor(
454            new BridgeMethodFixer()));
455
456        // Rename the source file attributes, if requested.
457        if (configuration.newSourceFileAttribute != null)
458        {
459            programClassPool.classesAccept(new SourceFileRenamer(configuration.newSourceFileAttribute));
460        }
461
462        // Remove unused constants.
463        programClassPool.classesAccept(
464            new ConstantPoolShrinker());
465    }
466}
467