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