ClassObfuscator.java revision 9f606f95f03a75961498803e24bee6799a7c0885
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.classfile.*;
24import proguard.classfile.attribute.*;
25import proguard.classfile.attribute.visitor.*;
26import proguard.classfile.constant.ClassConstant;
27import proguard.classfile.constant.visitor.ConstantVisitor;
28import proguard.classfile.util.*;
29import proguard.classfile.visitor.ClassVisitor;
30import proguard.util.*;
31
32import java.util.*;
33
34/**
35 * This <code>ClassVisitor</code> comes up with obfuscated names for the
36 * classes it visits, and for their class members. The actual renaming is
37 * done afterward.
38 *
39 * @see ClassRenamer
40 *
41 * @author Eric Lafortune
42 */
43public class ClassObfuscator
44extends      SimplifiedVisitor
45implements   ClassVisitor,
46             AttributeVisitor,
47             InnerClassesInfoVisitor,
48             ConstantVisitor
49{
50    private final DictionaryNameFactory classNameFactory;
51    private final DictionaryNameFactory packageNameFactory;
52    private final boolean               useMixedCaseClassNames;
53    private final StringMatcher         keepPackageNamesMatcher;
54    private final String                flattenPackageHierarchy;
55    private final String                repackageClasses;
56    private final boolean               allowAccessModification;
57
58    private final Set classNamesToAvoid                       = new HashSet();
59
60    // Map: [package prefix - new package prefix]
61    private final Map packagePrefixMap                        = new HashMap();
62
63    // Map: [package prefix - package name factory]
64    private final Map packagePrefixPackageNameFactoryMap      = new HashMap();
65
66    // Map: [package prefix - numeric class name factory]
67    private final Map packagePrefixClassNameFactoryMap        = new HashMap();
68
69    // Map: [package prefix - numeric class name factory]
70    private final Map packagePrefixNumericClassNameFactoryMap = new HashMap();
71
72    // Field acting as temporary variables and as return values for names
73    // of outer classes and types of inner classes.
74    private String  newClassName;
75    private boolean numericClassName;
76
77
78    /**
79     * Creates a new ClassObfuscator.
80     * @param programClassPool        the class pool in which class names
81     *                                have to be unique.
82     * @param classNameFactory        the optional class obfuscation dictionary.
83     * @param packageNameFactory      the optional package obfuscation
84     *                                dictionary.
85     * @param useMixedCaseClassNames  specifies whether obfuscated packages and
86     *                                classes can get mixed-case names.
87     * @param keepPackageNames        the optional filter for which matching
88     *                                package names are kept.
89     * @param flattenPackageHierarchy the base package if the obfuscated package
90     *                                hierarchy is to be flattened.
91     * @param repackageClasses        the base package if the obfuscated classes
92     *                                are to be repackaged.
93     * @param allowAccessModification specifies whether obfuscated classes can
94     *                                be freely moved between packages.
95     */
96    public ClassObfuscator(ClassPool             programClassPool,
97                           DictionaryNameFactory classNameFactory,
98                           DictionaryNameFactory packageNameFactory,
99                           boolean               useMixedCaseClassNames,
100                           List                  keepPackageNames,
101                           String                flattenPackageHierarchy,
102                           String                repackageClasses,
103                           boolean               allowAccessModification)
104    {
105        this.classNameFactory   = classNameFactory;
106        this.packageNameFactory = packageNameFactory;
107
108        // First append the package separator if necessary.
109        if (flattenPackageHierarchy != null &&
110            flattenPackageHierarchy.length() > 0)
111        {
112            flattenPackageHierarchy += ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
113        }
114
115        // First append the package separator if necessary.
116        if (repackageClasses != null &&
117            repackageClasses.length() > 0)
118        {
119            repackageClasses += ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
120        }
121
122        this.useMixedCaseClassNames  = useMixedCaseClassNames;
123        this.keepPackageNamesMatcher = keepPackageNames == null ? null :
124            new ListParser(new FileNameParser()).parse(keepPackageNames);
125        this.flattenPackageHierarchy = flattenPackageHierarchy;
126        this.repackageClasses        = repackageClasses;
127        this.allowAccessModification = allowAccessModification;
128
129        // Map the root package onto the root package.
130        packagePrefixMap.put("", "");
131
132        // Collect all names that have been taken already.
133        programClassPool.classesAccept(new MyKeepCollector());
134    }
135
136
137    // Implementations for ClassVisitor.
138
139    public void visitProgramClass(ProgramClass programClass)
140    {
141        // Does this class still need a new name?
142        newClassName = newClassName(programClass);
143        if (newClassName == null)
144        {
145            // Make sure the outer class has a name, if it exists. The name will
146            // be stored as the new class name, as a side effect, so we'll be
147            // able to use it as a prefix.
148            programClass.attributesAccept(this);
149
150            // Figure out a package prefix. The package prefix may actually be
151            // the an outer class prefix, if any, or it may be the fixed base
152            // package, if classes are to be repackaged.
153            String newPackagePrefix = newClassName != null ?
154                newClassName + ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR :
155                newPackagePrefix(ClassUtil.internalPackagePrefix(programClass.getName()));
156
157            // Come up with a new class name, numeric or ordinary.
158            newClassName = newClassName != null && numericClassName ?
159                generateUniqueNumericClassName(newPackagePrefix) :
160                generateUniqueClassName(newPackagePrefix);
161
162            setNewClassName(programClass, newClassName);
163        }
164    }
165
166
167    // Implementations for AttributeVisitor.
168
169    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
170
171
172    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
173    {
174        // Make sure the outer classes have a name, if they exist.
175        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
176    }
177
178
179    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
180    {
181        // Make sure the enclosing class has a name.
182        enclosingMethodAttribute.referencedClassAccept(this);
183
184        String innerClassName = clazz.getName();
185        String outerClassName = clazz.getClassName(enclosingMethodAttribute.u2classIndex);
186
187        numericClassName = isNumericClassName(innerClassName,
188                                              outerClassName);
189    }
190
191
192    // Implementations for InnerClassesInfoVisitor.
193
194    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
195    {
196        // Make sure the outer class has a name, if it exists.
197        int innerClassIndex = innerClassesInfo.u2innerClassIndex;
198        int outerClassIndex = innerClassesInfo.u2outerClassIndex;
199        if (innerClassIndex != 0 &&
200            outerClassIndex != 0)
201        {
202            String innerClassName = clazz.getClassName(innerClassIndex);
203            if (innerClassName.equals(clazz.getName()))
204            {
205                clazz.constantPoolEntryAccept(outerClassIndex, this);
206
207                String outerClassName = clazz.getClassName(outerClassIndex);
208
209                numericClassName = isNumericClassName(innerClassName,
210                                                      outerClassName);
211            }
212        }
213    }
214
215
216    /**
217     * Returns whether the given inner class name is a numeric name.
218     */
219    private boolean isNumericClassName(String innerClassName,
220                                       String outerClassName)
221    {
222        int innerClassNameStart  = outerClassName.length() + 1;
223        int innerClassNameLength = innerClassName.length();
224
225        if (innerClassNameStart >= innerClassNameLength)
226        {
227            return false;
228        }
229
230        for (int index = innerClassNameStart; index < innerClassNameLength; index++)
231        {
232            if (!Character.isDigit(innerClassName.charAt(index)))
233            {
234                return false;
235            }
236        }
237
238        return true;
239    }
240
241
242    // Implementations for ConstantVisitor.
243
244    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
245    {
246        // Make sure the outer class has a name.
247        classConstant.referencedClassAccept(this);
248    }
249
250
251    /**
252     * This ClassVisitor collects package names and class names that have to
253     * be kept.
254     */
255    private class MyKeepCollector implements ClassVisitor
256    {
257        public void visitProgramClass(ProgramClass programClass)
258        {
259            // Does the class already have a new name?
260            String newClassName = newClassName(programClass);
261            if (newClassName != null)
262            {
263                // Remember not to use this name.
264                classNamesToAvoid.add(mixedCaseClassName(newClassName));
265
266                // Are we not aggressively repackaging all obfuscated classes?
267                if (repackageClasses == null ||
268                    !allowAccessModification)
269                {
270                    String className = programClass.getName();
271
272                    // Keep the package name for all other classes in the same
273                    // package. Do this recursively if we're not doing any
274                    // repackaging.
275                    mapPackageName(className,
276                                   newClassName,
277                                   repackageClasses        == null &&
278                                   flattenPackageHierarchy == null);
279                }
280            }
281        }
282
283
284        public void visitLibraryClass(LibraryClass libraryClass)
285        {
286        }
287
288
289        /**
290         * Makes sure the package name of the given class will always be mapped
291         * consistently with its new name.
292         */
293        private void mapPackageName(String  className,
294                                    String  newClassName,
295                                    boolean recursively)
296        {
297            String packagePrefix    = ClassUtil.internalPackagePrefix(className);
298            String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
299
300            // Put the mapping of this package prefix, and possibly of its
301            // entire hierarchy, into the package prefix map.
302            do
303            {
304                packagePrefixMap.put(packagePrefix, newPackagePrefix);
305
306                if (!recursively)
307                {
308                    break;
309                }
310
311                packagePrefix    = ClassUtil.internalPackagePrefix(packagePrefix);
312                newPackagePrefix = ClassUtil.internalPackagePrefix(newPackagePrefix);
313            }
314            while (packagePrefix.length()    > 0 &&
315                   newPackagePrefix.length() > 0);
316        }
317    }
318
319
320    // Small utility methods.
321
322    /**
323     * Finds or creates the new package prefix for the given package.
324     */
325    private String newPackagePrefix(String packagePrefix)
326    {
327        // Doesn't the package prefix have a new package prefix yet?
328        String newPackagePrefix = (String)packagePrefixMap.get(packagePrefix);
329        if (newPackagePrefix == null)
330        {
331            // Are we keeping the package name?
332            if (keepPackageNamesMatcher != null &&
333                keepPackageNamesMatcher.matches(packagePrefix.length() > 0 ?
334                    packagePrefix.substring(0, packagePrefix.length()-1) :
335                    packagePrefix))
336            {
337                return packagePrefix;
338            }
339
340            // Are we forcing a new package prefix?
341            if (repackageClasses != null)
342            {
343                return repackageClasses;
344            }
345
346            // Are we forcing a new superpackage prefix?
347            // Otherwise figure out the new superpackage prefix, recursively.
348            String newSuperPackagePrefix = flattenPackageHierarchy != null ?
349                flattenPackageHierarchy :
350                newPackagePrefix(ClassUtil.internalPackagePrefix(packagePrefix));
351
352            // Come up with a new package prefix.
353            newPackagePrefix = generateUniquePackagePrefix(newSuperPackagePrefix);
354
355            // Remember to use this mapping in the future.
356            packagePrefixMap.put(packagePrefix, newPackagePrefix);
357        }
358
359        return newPackagePrefix;
360    }
361
362
363    /**
364     * Creates a new package prefix in the given new superpackage.
365     */
366    private String generateUniquePackagePrefix(String newSuperPackagePrefix)
367    {
368        // Find the right name factory for this package.
369        NameFactory packageNameFactory =
370            (NameFactory)packagePrefixPackageNameFactoryMap.get(newSuperPackagePrefix);
371        if (packageNameFactory == null)
372        {
373            // We haven't seen packages in this superpackage before. Create
374            // a new name factory for them.
375            packageNameFactory = new SimpleNameFactory(useMixedCaseClassNames);
376            if (this.packageNameFactory != null)
377            {
378                packageNameFactory =
379                    new DictionaryNameFactory(this.packageNameFactory,
380                                              packageNameFactory);
381            }
382
383            packagePrefixPackageNameFactoryMap.put(newSuperPackagePrefix,
384                                                   packageNameFactory);
385        }
386
387        return generateUniquePackagePrefix(newSuperPackagePrefix, packageNameFactory);
388    }
389
390
391    /**
392     * Creates a new package prefix in the given new superpackage, with the
393     * given package name factory.
394     */
395    private String generateUniquePackagePrefix(String      newSuperPackagePrefix,
396                                               NameFactory packageNameFactory)
397    {
398        // Come up with package names until we get an original one.
399        String newPackagePrefix;
400        do
401        {
402            // Let the factory produce a package name.
403            newPackagePrefix = newSuperPackagePrefix +
404                               packageNameFactory.nextName() +
405                               ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
406        }
407        while (packagePrefixMap.containsValue(newPackagePrefix));
408
409        return newPackagePrefix;
410    }
411
412
413    /**
414     * Creates a new class name in the given new package.
415     */
416    private String generateUniqueClassName(String newPackagePrefix)
417    {
418        // Find the right name factory for this package.
419        NameFactory classNameFactory =
420            (NameFactory)packagePrefixClassNameFactoryMap.get(newPackagePrefix);
421        if (classNameFactory == null)
422        {
423            // We haven't seen classes in this package before.
424            // Create a new name factory for them.
425            classNameFactory = new SimpleNameFactory(useMixedCaseClassNames);
426            if (this.classNameFactory != null)
427            {
428                classNameFactory =
429                    new DictionaryNameFactory(this.classNameFactory,
430                                              classNameFactory);
431            }
432
433            packagePrefixClassNameFactoryMap.put(newPackagePrefix,
434                                                 classNameFactory);
435        }
436
437        return generateUniqueClassName(newPackagePrefix, classNameFactory);
438    }
439
440
441    /**
442     * Creates a new class name in the given new package.
443     */
444    private String generateUniqueNumericClassName(String newPackagePrefix)
445    {
446        // Find the right name factory for this package.
447        NameFactory classNameFactory =
448            (NameFactory)packagePrefixNumericClassNameFactoryMap.get(newPackagePrefix);
449        if (classNameFactory == null)
450        {
451            // We haven't seen classes in this package before.
452            // Create a new name factory for them.
453            classNameFactory = new NumericNameFactory();
454
455            packagePrefixNumericClassNameFactoryMap.put(newPackagePrefix,
456                                                        classNameFactory);
457        }
458
459        return generateUniqueClassName(newPackagePrefix, classNameFactory);
460    }
461
462
463    /**
464     * Creates a new class name in the given new package, with the given
465     * class name factory.
466     */
467    private String generateUniqueClassName(String      newPackagePrefix,
468                                           NameFactory classNameFactory)
469    {
470        // Come up with class names until we get an original one.
471        String newClassName;
472        do
473        {
474            // Let the factory produce a class name.
475            newClassName = newPackagePrefix +
476                           classNameFactory.nextName();
477        }
478        while (classNamesToAvoid.contains(mixedCaseClassName(newClassName)));
479
480        return newClassName;
481    }
482
483
484    /**
485     * Returns the given class name, unchanged if mixed-case class names are
486     * allowed, or the lower-case version otherwise.
487     */
488    private String mixedCaseClassName(String className)
489    {
490        return useMixedCaseClassNames ?
491            className :
492            className.toLowerCase();
493    }
494
495
496    /**
497     * Assigns a new name to the given class.
498     * @param clazz the given class.
499     * @param name  the new name.
500     */
501    static void setNewClassName(Clazz clazz, String name)
502    {
503        clazz.setVisitorInfo(name);
504    }
505
506
507    /**
508     * Retrieves the new name of the given class.
509     * @param clazz the given class.
510     * @return the class's new name, or <code>null</code> if it doesn't
511     *         have one yet.
512     */
513    static String newClassName(Clazz clazz)
514    {
515        Object visitorInfo = clazz.getVisitorInfo();
516
517        return visitorInfo instanceof String ?
518            (String)visitorInfo :
519            null;
520    }
521}
522