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.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.PACKAGE_SEPARATOR;
113        }
114
115        // First append the package separator if necessary.
116        if (repackageClasses != null &&
117            repackageClasses.length() > 0)
118        {
119            repackageClasses += ClassConstants.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.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    public void visitLibraryClass(LibraryClass libraryClass)
168    {
169        // This can happen for dubious input, if the outer class of a program
170        // class is a library class, and its name is requested.
171        newClassName = libraryClass.getName();
172    }
173
174
175    // Implementations for AttributeVisitor.
176
177    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
178
179
180    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
181    {
182        // Make sure the outer classes have a name, if they exist.
183        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
184    }
185
186
187    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
188    {
189        // Make sure the enclosing class has a name.
190        enclosingMethodAttribute.referencedClassAccept(this);
191
192        String innerClassName = clazz.getName();
193        String outerClassName = clazz.getClassName(enclosingMethodAttribute.u2classIndex);
194
195        numericClassName = isNumericClassName(innerClassName,
196                                              outerClassName);
197    }
198
199
200    // Implementations for InnerClassesInfoVisitor.
201
202    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
203    {
204        // Make sure the outer class has a name, if it exists.
205        int innerClassIndex = innerClassesInfo.u2innerClassIndex;
206        int outerClassIndex = innerClassesInfo.u2outerClassIndex;
207        if (innerClassIndex != 0 &&
208            outerClassIndex != 0)
209        {
210            String innerClassName = clazz.getClassName(innerClassIndex);
211            if (innerClassName.equals(clazz.getName()))
212            {
213                clazz.constantPoolEntryAccept(outerClassIndex, this);
214
215                String outerClassName = clazz.getClassName(outerClassIndex);
216
217                numericClassName = isNumericClassName(innerClassName,
218                                                      outerClassName);
219            }
220        }
221    }
222
223
224    /**
225     * Returns whether the given inner class name is a numeric name.
226     */
227    private boolean isNumericClassName(String innerClassName,
228                                       String outerClassName)
229    {
230        int innerClassNameStart  = outerClassName.length() + 1;
231        int innerClassNameLength = innerClassName.length();
232
233        if (innerClassNameStart >= innerClassNameLength)
234        {
235            return false;
236        }
237
238        for (int index = innerClassNameStart; index < innerClassNameLength; index++)
239        {
240            if (!Character.isDigit(innerClassName.charAt(index)))
241            {
242                return false;
243            }
244        }
245
246        return true;
247    }
248
249
250    // Implementations for ConstantVisitor.
251
252    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
253    {
254        // Make sure the outer class has a name.
255        classConstant.referencedClassAccept(this);
256    }
257
258
259    /**
260     * This ClassVisitor collects package names and class names that have to
261     * be kept.
262     */
263    private class MyKeepCollector implements ClassVisitor
264    {
265        public void visitProgramClass(ProgramClass programClass)
266        {
267            // Does the class already have a new name?
268            String newClassName = newClassName(programClass);
269            if (newClassName != null)
270            {
271                // Remember not to use this name.
272                classNamesToAvoid.add(mixedCaseClassName(newClassName));
273
274                // Are we not aggressively repackaging all obfuscated classes?
275                if (repackageClasses == null ||
276                    !allowAccessModification)
277                {
278                    String className = programClass.getName();
279
280                    // Keep the package name for all other classes in the same
281                    // package. Do this recursively if we're not doing any
282                    // repackaging.
283                    mapPackageName(className,
284                                   newClassName,
285                                   repackageClasses        == null &&
286                                   flattenPackageHierarchy == null);
287                }
288            }
289        }
290
291
292        public void visitLibraryClass(LibraryClass libraryClass)
293        {
294        }
295
296
297        /**
298         * Makes sure the package name of the given class will always be mapped
299         * consistently with its new name.
300         */
301        private void mapPackageName(String  className,
302                                    String  newClassName,
303                                    boolean recursively)
304        {
305            String packagePrefix    = ClassUtil.internalPackagePrefix(className);
306            String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
307
308            // Put the mapping of this package prefix, and possibly of its
309            // entire hierarchy, into the package prefix map.
310            do
311            {
312                packagePrefixMap.put(packagePrefix, newPackagePrefix);
313
314                if (!recursively)
315                {
316                    break;
317                }
318
319                packagePrefix    = ClassUtil.internalPackagePrefix(packagePrefix);
320                newPackagePrefix = ClassUtil.internalPackagePrefix(newPackagePrefix);
321            }
322            while (packagePrefix.length()    > 0 &&
323                   newPackagePrefix.length() > 0);
324        }
325    }
326
327
328    // Small utility methods.
329
330    /**
331     * Finds or creates the new package prefix for the given package.
332     */
333    private String newPackagePrefix(String packagePrefix)
334    {
335        // Doesn't the package prefix have a new package prefix yet?
336        String newPackagePrefix = (String)packagePrefixMap.get(packagePrefix);
337        if (newPackagePrefix == null)
338        {
339            // Are we keeping the package name?
340            if (keepPackageNamesMatcher != null &&
341                keepPackageNamesMatcher.matches(packagePrefix.length() > 0 ?
342                    packagePrefix.substring(0, packagePrefix.length()-1) :
343                    packagePrefix))
344            {
345                return packagePrefix;
346            }
347
348            // Are we forcing a new package prefix?
349            if (repackageClasses != null)
350            {
351                return repackageClasses;
352            }
353
354            // Are we forcing a new superpackage prefix?
355            // Otherwise figure out the new superpackage prefix, recursively.
356            String newSuperPackagePrefix = flattenPackageHierarchy != null ?
357                flattenPackageHierarchy :
358                newPackagePrefix(ClassUtil.internalPackagePrefix(packagePrefix));
359
360            // Come up with a new package prefix.
361            newPackagePrefix = generateUniquePackagePrefix(newSuperPackagePrefix);
362
363            // Remember to use this mapping in the future.
364            packagePrefixMap.put(packagePrefix, newPackagePrefix);
365        }
366
367        return newPackagePrefix;
368    }
369
370
371    /**
372     * Creates a new package prefix in the given new superpackage.
373     */
374    private String generateUniquePackagePrefix(String newSuperPackagePrefix)
375    {
376        // Find the right name factory for this package.
377        NameFactory packageNameFactory =
378            (NameFactory)packagePrefixPackageNameFactoryMap.get(newSuperPackagePrefix);
379        if (packageNameFactory == null)
380        {
381            // We haven't seen packages in this superpackage before. Create
382            // a new name factory for them.
383            packageNameFactory = new SimpleNameFactory(useMixedCaseClassNames);
384            if (this.packageNameFactory != null)
385            {
386                packageNameFactory =
387                    new DictionaryNameFactory(this.packageNameFactory,
388                                              packageNameFactory);
389            }
390
391            packagePrefixPackageNameFactoryMap.put(newSuperPackagePrefix,
392                                                   packageNameFactory);
393        }
394
395        return generateUniquePackagePrefix(newSuperPackagePrefix, packageNameFactory);
396    }
397
398
399    /**
400     * Creates a new package prefix in the given new superpackage, with the
401     * given package name factory.
402     */
403    private String generateUniquePackagePrefix(String      newSuperPackagePrefix,
404                                               NameFactory packageNameFactory)
405    {
406        // Come up with package names until we get an original one.
407        String newPackagePrefix;
408        do
409        {
410            // Let the factory produce a package name.
411            newPackagePrefix = newSuperPackagePrefix +
412                               packageNameFactory.nextName() +
413                               ClassConstants.PACKAGE_SEPARATOR;
414        }
415        while (packagePrefixMap.containsValue(newPackagePrefix));
416
417        return newPackagePrefix;
418    }
419
420
421    /**
422     * Creates a new class name in the given new package.
423     */
424    private String generateUniqueClassName(String newPackagePrefix)
425    {
426        // Find the right name factory for this package.
427        NameFactory classNameFactory =
428            (NameFactory)packagePrefixClassNameFactoryMap.get(newPackagePrefix);
429        if (classNameFactory == null)
430        {
431            // We haven't seen classes in this package before.
432            // Create a new name factory for them.
433            classNameFactory = new SimpleNameFactory(useMixedCaseClassNames);
434            if (this.classNameFactory != null)
435            {
436                classNameFactory =
437                    new DictionaryNameFactory(this.classNameFactory,
438                                              classNameFactory);
439            }
440
441            packagePrefixClassNameFactoryMap.put(newPackagePrefix,
442                                                 classNameFactory);
443        }
444
445        return generateUniqueClassName(newPackagePrefix, classNameFactory);
446    }
447
448
449    /**
450     * Creates a new class name in the given new package.
451     */
452    private String generateUniqueNumericClassName(String newPackagePrefix)
453    {
454        // Find the right name factory for this package.
455        NameFactory classNameFactory =
456            (NameFactory)packagePrefixNumericClassNameFactoryMap.get(newPackagePrefix);
457        if (classNameFactory == null)
458        {
459            // We haven't seen classes in this package before.
460            // Create a new name factory for them.
461            classNameFactory = new NumericNameFactory();
462
463            packagePrefixNumericClassNameFactoryMap.put(newPackagePrefix,
464                                                        classNameFactory);
465        }
466
467        return generateUniqueClassName(newPackagePrefix, classNameFactory);
468    }
469
470
471    /**
472     * Creates a new class name in the given new package, with the given
473     * class name factory.
474     */
475    private String generateUniqueClassName(String      newPackagePrefix,
476                                           NameFactory classNameFactory)
477    {
478        // Come up with class names until we get an original one.
479        String newClassName;
480        String newMixedCaseClassName;
481        do
482        {
483            // Let the factory produce a class name.
484            newClassName = newPackagePrefix +
485                           classNameFactory.nextName();
486
487            newMixedCaseClassName = mixedCaseClassName(newClassName);
488        }
489        while (classNamesToAvoid.contains(newMixedCaseClassName));
490
491        // Explicitly make sure the name isn't used again if we have a
492        // user-specified dictionary and we're not allowed to have mixed case
493        // class names -- just to protect against problematic dictionaries.
494        if (this.classNameFactory != null &&
495            !useMixedCaseClassNames)
496        {
497            classNamesToAvoid.add(newMixedCaseClassName);
498        }
499
500        return newClassName;
501    }
502
503
504    /**
505     * Returns the given class name, unchanged if mixed-case class names are
506     * allowed, or the lower-case version otherwise.
507     */
508    private String mixedCaseClassName(String className)
509    {
510        return useMixedCaseClassNames ?
511            className :
512            className.toLowerCase();
513    }
514
515
516    /**
517     * Assigns a new name to the given class.
518     * @param clazz the given class.
519     * @param name  the new name.
520     */
521    static void setNewClassName(Clazz clazz, String name)
522    {
523        clazz.setVisitorInfo(name);
524    }
525
526
527    /**
528     * Retrieves the new name of the given class.
529     * @param clazz the given class.
530     * @return the class's new name, or <code>null</code> if it doesn't
531     *         have one yet.
532     */
533    static String newClassName(Clazz clazz)
534    {
535        Object visitorInfo = clazz.getVisitorInfo();
536
537        return visitorInfo instanceof String ?
538            (String)visitorInfo :
539            null;
540    }
541}
542