| /* |
| * ProGuard -- shrinking, optimization, obfuscation, and preverification |
| * of Java bytecode. |
| * |
| * Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu) |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| package proguard.obfuscate; |
| |
| import proguard.classfile.*; |
| import proguard.classfile.attribute.*; |
| import proguard.classfile.attribute.visitor.*; |
| import proguard.classfile.constant.ClassConstant; |
| import proguard.classfile.constant.visitor.ConstantVisitor; |
| import proguard.classfile.util.*; |
| import proguard.classfile.visitor.ClassVisitor; |
| import proguard.util.*; |
| |
| import java.util.*; |
| |
| /** |
| * This <code>ClassVisitor</code> comes up with obfuscated names for the |
| * classes it visits, and for their class members. The actual renaming is |
| * done afterward. |
| * |
| * @see ClassRenamer |
| * |
| * @author Eric Lafortune |
| */ |
| public class ClassObfuscator |
| extends SimplifiedVisitor |
| implements ClassVisitor, |
| AttributeVisitor, |
| InnerClassesInfoVisitor, |
| ConstantVisitor |
| { |
| private final DictionaryNameFactory classNameFactory; |
| private final DictionaryNameFactory packageNameFactory; |
| private final boolean useMixedCaseClassNames; |
| private final StringMatcher keepPackageNamesMatcher; |
| private final String flattenPackageHierarchy; |
| private final String repackageClasses; |
| private final boolean allowAccessModification; |
| |
| private final Set classNamesToAvoid = new HashSet(); |
| |
| // Map: [package prefix - new package prefix] |
| private final Map packagePrefixMap = new HashMap(); |
| |
| // Map: [package prefix - package name factory] |
| private final Map packagePrefixPackageNameFactoryMap = new HashMap(); |
| |
| // Map: [package prefix - numeric class name factory] |
| private final Map packagePrefixClassNameFactoryMap = new HashMap(); |
| |
| // Map: [package prefix - numeric class name factory] |
| private final Map packagePrefixNumericClassNameFactoryMap = new HashMap(); |
| |
| // Field acting as temporary variables and as return values for names |
| // of outer classes and types of inner classes. |
| private String newClassName; |
| private boolean numericClassName; |
| |
| |
| /** |
| * Creates a new ClassObfuscator. |
| * @param programClassPool the class pool in which class names |
| * have to be unique. |
| * @param classNameFactory the optional class obfuscation dictionary. |
| * @param packageNameFactory the optional package obfuscation |
| * dictionary. |
| * @param useMixedCaseClassNames specifies whether obfuscated packages and |
| * classes can get mixed-case names. |
| * @param keepPackageNames the optional filter for which matching |
| * package names are kept. |
| * @param flattenPackageHierarchy the base package if the obfuscated package |
| * hierarchy is to be flattened. |
| * @param repackageClasses the base package if the obfuscated classes |
| * are to be repackaged. |
| * @param allowAccessModification specifies whether obfuscated classes can |
| * be freely moved between packages. |
| */ |
| public ClassObfuscator(ClassPool programClassPool, |
| DictionaryNameFactory classNameFactory, |
| DictionaryNameFactory packageNameFactory, |
| boolean useMixedCaseClassNames, |
| List keepPackageNames, |
| String flattenPackageHierarchy, |
| String repackageClasses, |
| boolean allowAccessModification) |
| { |
| this.classNameFactory = classNameFactory; |
| this.packageNameFactory = packageNameFactory; |
| |
| // First append the package separator if necessary. |
| if (flattenPackageHierarchy != null && |
| flattenPackageHierarchy.length() > 0) |
| { |
| flattenPackageHierarchy += ClassConstants.PACKAGE_SEPARATOR; |
| } |
| |
| // First append the package separator if necessary. |
| if (repackageClasses != null && |
| repackageClasses.length() > 0) |
| { |
| repackageClasses += ClassConstants.PACKAGE_SEPARATOR; |
| } |
| |
| this.useMixedCaseClassNames = useMixedCaseClassNames; |
| this.keepPackageNamesMatcher = keepPackageNames == null ? null : |
| new ListParser(new FileNameParser()).parse(keepPackageNames); |
| this.flattenPackageHierarchy = flattenPackageHierarchy; |
| this.repackageClasses = repackageClasses; |
| this.allowAccessModification = allowAccessModification; |
| |
| // Map the root package onto the root package. |
| packagePrefixMap.put("", ""); |
| |
| // Collect all names that have been taken already. |
| programClassPool.classesAccept(new MyKeepCollector()); |
| } |
| |
| |
| // Implementations for ClassVisitor. |
| |
| public void visitProgramClass(ProgramClass programClass) |
| { |
| // Does this class still need a new name? |
| newClassName = newClassName(programClass); |
| if (newClassName == null) |
| { |
| // Make sure the outer class has a name, if it exists. The name will |
| // be stored as the new class name, as a side effect, so we'll be |
| // able to use it as a prefix. |
| programClass.attributesAccept(this); |
| |
| // Figure out a package prefix. The package prefix may actually be |
| // the an outer class prefix, if any, or it may be the fixed base |
| // package, if classes are to be repackaged. |
| String newPackagePrefix = newClassName != null ? |
| newClassName + ClassConstants.INNER_CLASS_SEPARATOR : |
| newPackagePrefix(ClassUtil.internalPackagePrefix(programClass.getName())); |
| |
| // Come up with a new class name, numeric or ordinary. |
| newClassName = newClassName != null && numericClassName ? |
| generateUniqueNumericClassName(newPackagePrefix) : |
| generateUniqueClassName(newPackagePrefix); |
| |
| setNewClassName(programClass, newClassName); |
| } |
| } |
| |
| |
| public void visitLibraryClass(LibraryClass libraryClass) |
| { |
| // This can happen for dubious input, if the outer class of a program |
| // class is a library class, and its name is requested. |
| newClassName = libraryClass.getName(); |
| } |
| |
| |
| // Implementations for AttributeVisitor. |
| |
| public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} |
| |
| |
| public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) |
| { |
| // Make sure the outer classes have a name, if they exist. |
| innerClassesAttribute.innerClassEntriesAccept(clazz, this); |
| } |
| |
| |
| public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) |
| { |
| // Make sure the enclosing class has a name. |
| enclosingMethodAttribute.referencedClassAccept(this); |
| |
| String innerClassName = clazz.getName(); |
| String outerClassName = clazz.getClassName(enclosingMethodAttribute.u2classIndex); |
| |
| numericClassName = isNumericClassName(innerClassName, |
| outerClassName); |
| } |
| |
| |
| // Implementations for InnerClassesInfoVisitor. |
| |
| public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) |
| { |
| // Make sure the outer class has a name, if it exists. |
| int innerClassIndex = innerClassesInfo.u2innerClassIndex; |
| int outerClassIndex = innerClassesInfo.u2outerClassIndex; |
| if (innerClassIndex != 0 && |
| outerClassIndex != 0) |
| { |
| String innerClassName = clazz.getClassName(innerClassIndex); |
| if (innerClassName.equals(clazz.getName())) |
| { |
| clazz.constantPoolEntryAccept(outerClassIndex, this); |
| |
| String outerClassName = clazz.getClassName(outerClassIndex); |
| |
| numericClassName = isNumericClassName(innerClassName, |
| outerClassName); |
| } |
| } |
| } |
| |
| |
| /** |
| * Returns whether the given inner class name is a numeric name. |
| */ |
| private boolean isNumericClassName(String innerClassName, |
| String outerClassName) |
| { |
| int innerClassNameStart = outerClassName.length() + 1; |
| int innerClassNameLength = innerClassName.length(); |
| |
| if (innerClassNameStart >= innerClassNameLength) |
| { |
| return false; |
| } |
| |
| for (int index = innerClassNameStart; index < innerClassNameLength; index++) |
| { |
| if (!Character.isDigit(innerClassName.charAt(index))) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| // Implementations for ConstantVisitor. |
| |
| public void visitClassConstant(Clazz clazz, ClassConstant classConstant) |
| { |
| // Make sure the outer class has a name. |
| classConstant.referencedClassAccept(this); |
| } |
| |
| |
| /** |
| * This ClassVisitor collects package names and class names that have to |
| * be kept. |
| */ |
| private class MyKeepCollector implements ClassVisitor |
| { |
| public void visitProgramClass(ProgramClass programClass) |
| { |
| // Does the class already have a new name? |
| String newClassName = newClassName(programClass); |
| if (newClassName != null) |
| { |
| // Remember not to use this name. |
| classNamesToAvoid.add(mixedCaseClassName(newClassName)); |
| |
| // Are we not aggressively repackaging all obfuscated classes? |
| if (repackageClasses == null || |
| !allowAccessModification) |
| { |
| String className = programClass.getName(); |
| |
| // Keep the package name for all other classes in the same |
| // package. Do this recursively if we're not doing any |
| // repackaging. |
| mapPackageName(className, |
| newClassName, |
| repackageClasses == null && |
| flattenPackageHierarchy == null); |
| } |
| } |
| } |
| |
| |
| public void visitLibraryClass(LibraryClass libraryClass) |
| { |
| } |
| |
| |
| /** |
| * Makes sure the package name of the given class will always be mapped |
| * consistently with its new name. |
| */ |
| private void mapPackageName(String className, |
| String newClassName, |
| boolean recursively) |
| { |
| String packagePrefix = ClassUtil.internalPackagePrefix(className); |
| String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName); |
| |
| // Put the mapping of this package prefix, and possibly of its |
| // entire hierarchy, into the package prefix map. |
| do |
| { |
| packagePrefixMap.put(packagePrefix, newPackagePrefix); |
| |
| if (!recursively) |
| { |
| break; |
| } |
| |
| packagePrefix = ClassUtil.internalPackagePrefix(packagePrefix); |
| newPackagePrefix = ClassUtil.internalPackagePrefix(newPackagePrefix); |
| } |
| while (packagePrefix.length() > 0 && |
| newPackagePrefix.length() > 0); |
| } |
| } |
| |
| |
| // Small utility methods. |
| |
| /** |
| * Finds or creates the new package prefix for the given package. |
| */ |
| private String newPackagePrefix(String packagePrefix) |
| { |
| // Doesn't the package prefix have a new package prefix yet? |
| String newPackagePrefix = (String)packagePrefixMap.get(packagePrefix); |
| if (newPackagePrefix == null) |
| { |
| // Are we keeping the package name? |
| if (keepPackageNamesMatcher != null && |
| keepPackageNamesMatcher.matches(packagePrefix.length() > 0 ? |
| packagePrefix.substring(0, packagePrefix.length()-1) : |
| packagePrefix)) |
| { |
| return packagePrefix; |
| } |
| |
| // Are we forcing a new package prefix? |
| if (repackageClasses != null) |
| { |
| return repackageClasses; |
| } |
| |
| // Are we forcing a new superpackage prefix? |
| // Otherwise figure out the new superpackage prefix, recursively. |
| String newSuperPackagePrefix = flattenPackageHierarchy != null ? |
| flattenPackageHierarchy : |
| newPackagePrefix(ClassUtil.internalPackagePrefix(packagePrefix)); |
| |
| // Come up with a new package prefix. |
| newPackagePrefix = generateUniquePackagePrefix(newSuperPackagePrefix); |
| |
| // Remember to use this mapping in the future. |
| packagePrefixMap.put(packagePrefix, newPackagePrefix); |
| } |
| |
| return newPackagePrefix; |
| } |
| |
| |
| /** |
| * Creates a new package prefix in the given new superpackage. |
| */ |
| private String generateUniquePackagePrefix(String newSuperPackagePrefix) |
| { |
| // Find the right name factory for this package. |
| NameFactory packageNameFactory = |
| (NameFactory)packagePrefixPackageNameFactoryMap.get(newSuperPackagePrefix); |
| if (packageNameFactory == null) |
| { |
| // We haven't seen packages in this superpackage before. Create |
| // a new name factory for them. |
| packageNameFactory = new SimpleNameFactory(useMixedCaseClassNames); |
| if (this.packageNameFactory != null) |
| { |
| packageNameFactory = |
| new DictionaryNameFactory(this.packageNameFactory, |
| packageNameFactory); |
| } |
| |
| packagePrefixPackageNameFactoryMap.put(newSuperPackagePrefix, |
| packageNameFactory); |
| } |
| |
| return generateUniquePackagePrefix(newSuperPackagePrefix, packageNameFactory); |
| } |
| |
| |
| /** |
| * Creates a new package prefix in the given new superpackage, with the |
| * given package name factory. |
| */ |
| private String generateUniquePackagePrefix(String newSuperPackagePrefix, |
| NameFactory packageNameFactory) |
| { |
| // Come up with package names until we get an original one. |
| String newPackagePrefix; |
| do |
| { |
| // Let the factory produce a package name. |
| newPackagePrefix = newSuperPackagePrefix + |
| packageNameFactory.nextName() + |
| ClassConstants.PACKAGE_SEPARATOR; |
| } |
| while (packagePrefixMap.containsValue(newPackagePrefix)); |
| |
| return newPackagePrefix; |
| } |
| |
| |
| /** |
| * Creates a new class name in the given new package. |
| */ |
| private String generateUniqueClassName(String newPackagePrefix) |
| { |
| // Find the right name factory for this package. |
| NameFactory classNameFactory = |
| (NameFactory)packagePrefixClassNameFactoryMap.get(newPackagePrefix); |
| if (classNameFactory == null) |
| { |
| // We haven't seen classes in this package before. |
| // Create a new name factory for them. |
| classNameFactory = new SimpleNameFactory(useMixedCaseClassNames); |
| if (this.classNameFactory != null) |
| { |
| classNameFactory = |
| new DictionaryNameFactory(this.classNameFactory, |
| classNameFactory); |
| } |
| |
| packagePrefixClassNameFactoryMap.put(newPackagePrefix, |
| classNameFactory); |
| } |
| |
| return generateUniqueClassName(newPackagePrefix, classNameFactory); |
| } |
| |
| |
| /** |
| * Creates a new class name in the given new package. |
| */ |
| private String generateUniqueNumericClassName(String newPackagePrefix) |
| { |
| // Find the right name factory for this package. |
| NameFactory classNameFactory = |
| (NameFactory)packagePrefixNumericClassNameFactoryMap.get(newPackagePrefix); |
| if (classNameFactory == null) |
| { |
| // We haven't seen classes in this package before. |
| // Create a new name factory for them. |
| classNameFactory = new NumericNameFactory(); |
| |
| packagePrefixNumericClassNameFactoryMap.put(newPackagePrefix, |
| classNameFactory); |
| } |
| |
| return generateUniqueClassName(newPackagePrefix, classNameFactory); |
| } |
| |
| |
| /** |
| * Creates a new class name in the given new package, with the given |
| * class name factory. |
| */ |
| private String generateUniqueClassName(String newPackagePrefix, |
| NameFactory classNameFactory) |
| { |
| // Come up with class names until we get an original one. |
| String newClassName; |
| String newMixedCaseClassName; |
| do |
| { |
| // Let the factory produce a class name. |
| newClassName = newPackagePrefix + |
| classNameFactory.nextName(); |
| |
| newMixedCaseClassName = mixedCaseClassName(newClassName); |
| } |
| while (classNamesToAvoid.contains(newMixedCaseClassName)); |
| |
| // Explicitly make sure the name isn't used again if we have a |
| // user-specified dictionary and we're not allowed to have mixed case |
| // class names -- just to protect against problematic dictionaries. |
| if (this.classNameFactory != null && |
| !useMixedCaseClassNames) |
| { |
| classNamesToAvoid.add(newMixedCaseClassName); |
| } |
| |
| return newClassName; |
| } |
| |
| |
| /** |
| * Returns the given class name, unchanged if mixed-case class names are |
| * allowed, or the lower-case version otherwise. |
| */ |
| private String mixedCaseClassName(String className) |
| { |
| return useMixedCaseClassNames ? |
| className : |
| className.toLowerCase(); |
| } |
| |
| |
| /** |
| * Assigns a new name to the given class. |
| * @param clazz the given class. |
| * @param name the new name. |
| */ |
| static void setNewClassName(Clazz clazz, String name) |
| { |
| clazz.setVisitorInfo(name); |
| } |
| |
| |
| /** |
| * Retrieves the new name of the given class. |
| * @param clazz the given class. |
| * @return the class's new name, or <code>null</code> if it doesn't |
| * have one yet. |
| */ |
| static String newClassName(Clazz clazz) |
| { |
| Object visitorInfo = clazz.getVisitorInfo(); |
| |
| return visitorInfo instanceof String ? |
| (String)visitorInfo : |
| null; |
| } |
| } |