diff options
Diffstat (limited to 'third_party/java/proguard/proguard5.3.3/src/proguard/obfuscate/ClassObfuscator.java')
-rw-r--r-- | third_party/java/proguard/proguard5.3.3/src/proguard/obfuscate/ClassObfuscator.java | 569 |
1 files changed, 569 insertions, 0 deletions
diff --git a/third_party/java/proguard/proguard5.3.3/src/proguard/obfuscate/ClassObfuscator.java b/third_party/java/proguard/proguard5.3.3/src/proguard/obfuscate/ClassObfuscator.java new file mode 100644 index 0000000000..bafe1ffc1e --- /dev/null +++ b/third_party/java/proguard/proguard5.3.3/src/proguard/obfuscate/ClassObfuscator.java @@ -0,0 +1,569 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2017 Eric Lafortune @ GuardSquare + * + * 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 libraryClassPool the class pool from which class names + * have to be avoided. + * @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, + ClassPool libraryClassPool, + 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 already been taken. + programClassPool.classesAccept(new MyKeepCollector()); + libraryClassPool.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 program 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) + { + // Get the new name or the original name of the library class. + String newClassName = newClassName(libraryClass); + if (newClassName == null) + { + newClassName = libraryClass.getName(); + } + + // Remember not to use this name. + classNamesToAvoid.add(mixedCaseClassName(newClassName)); + + // Are we not aggressively repackaging all obfuscated classes? + if (repackageClasses == null || + !allowAccessModification) + { + String className = libraryClass.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); + } + } + + + /** + * 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; + } +} |