001: /*
002: * ProGuard -- shrinking, optimization, obfuscation, and preverification
003: * of Java bytecode.
004: *
005: * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
006: *
007: * This program is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License as published by the Free
009: * Software Foundation; either version 2 of the License, or (at your option)
010: * any later version.
011: *
012: * This program is distributed in the hope that it will be useful, but WITHOUT
013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
015: * more details.
016: *
017: * You should have received a copy of the GNU General Public License along
018: * with this program; if not, write to the Free Software Foundation, Inc.,
019: * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021: package proguard.obfuscate;
022:
023: import proguard.classfile.*;
024: import proguard.classfile.attribute.*;
025: import proguard.classfile.attribute.visitor.*;
026: import proguard.classfile.constant.ClassConstant;
027: import proguard.classfile.constant.visitor.ConstantVisitor;
028: import proguard.classfile.util.*;
029: import proguard.classfile.visitor.ClassVisitor;
030:
031: import java.util.*;
032:
033: /**
034: * This <code>ClassVisitor</code> comes up with obfuscated names for the
035: * classes it visits, and for their class members. The actual renaming is
036: * done afterward.
037: *
038: * @see ClassRenamer
039: *
040: * @author Eric Lafortune
041: */
042: public class ClassObfuscator extends SimplifiedVisitor implements
043: ClassVisitor, AttributeVisitor, InnerClassesInfoVisitor,
044: ConstantVisitor {
045: private final boolean useMixedCaseClassNames;
046: private final String flattenPackageHierarchy;
047: private final String repackageClasses;
048: private final boolean allowAccessModification;
049:
050: private final Set classNamesToAvoid = new HashSet();
051:
052: // Map: [package prefix - new package prefix]
053: private final Map packagePrefixMap = new HashMap();
054:
055: // Map: [package prefix - package name factory]
056: private final Map packagePrefixPackageNameFactoryMap = new HashMap();
057:
058: // Map: [package prefix - class name factory]
059: private final Map packagePrefixClassNameFactoryMap = new HashMap();
060:
061: // A field acting as a temporary variable and as a return value for names
062: // of outer classes.
063: private String newClassName;
064:
065: /**
066: * Creates a new ClassObfuscator.
067: * @param programClassPool the class pool in which class names
068: * have to be unique.
069: * @param useMixedCaseClassNames specifies whether obfuscated packages
070: * and classes can get mixed-case names.
071: * @param flattenPackageHierarchy the base package if the obfuscated
072: * package hierarchy is to be flattened.
073: * @param repackageClasses the base package if the obfuscated
074: * classes are to be repackaged.
075: * @param allowAccessModification specifies whether obfuscated classes
076: * can be freely moved between packages.
077: */
078: public ClassObfuscator(ClassPool programClassPool,
079: boolean useMixedCaseClassNames,
080: String flattenPackageHierarchy, String repackageClasses,
081: boolean allowAccessModification) {
082: // First append the package separator if necessary.
083: if (flattenPackageHierarchy != null
084: && flattenPackageHierarchy.length() > 0) {
085: flattenPackageHierarchy += ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
086: }
087:
088: // First append the package separator if necessary.
089: if (repackageClasses != null && repackageClasses.length() > 0) {
090: repackageClasses += ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
091: }
092:
093: this .useMixedCaseClassNames = useMixedCaseClassNames;
094: this .flattenPackageHierarchy = flattenPackageHierarchy;
095: this .repackageClasses = repackageClasses;
096: this .allowAccessModification = allowAccessModification;
097:
098: // Map the root package onto the root package.
099: packagePrefixMap.put("", "");
100:
101: // Collect all names that have been taken already.
102: programClassPool.classesAccept(new MyKeepCollector());
103: }
104:
105: // Implementations for ClassVisitor.
106:
107: public void visitProgramClass(ProgramClass programClass) {
108: // Does this class still need a new name?
109: newClassName = newClassName(programClass);
110: if (newClassName == null) {
111: // Make sure the outer class has a name, if it exists. The name will
112: // be stored as the new class name, as a side effect, so we'll be
113: // able to use it as a prefix.
114: programClass.attributesAccept(this );
115:
116: // Figure out a package prefix. The package prefix may actually be
117: // the outer class prefix, if any, or it may be the fixed base
118: // package, if classes are to be repackaged.
119: String newPackagePrefix = newClassName != null ? newClassName
120: + ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR
121: : newPackagePrefix(ClassUtil
122: .internalPackagePrefix(programClass
123: .getName()));
124:
125: // Come up with a new class name.
126: newClassName = generateUniqueClassName(newPackagePrefix);
127:
128: setNewClassName(programClass, newClassName);
129: }
130: }
131:
132: // Implementations for AttributeVisitor.
133:
134: public void visitAnyAttribute(Clazz clazz, Attribute attribute) {
135: }
136:
137: public void visitInnerClassesAttribute(Clazz clazz,
138: InnerClassesAttribute innerClassesAttribute) {
139: // Make sure the outer classes have a name, if they exist.
140: innerClassesAttribute.innerClassEntriesAccept(clazz, this );
141: }
142:
143: public void visitEnclosingMethodAttribute(Clazz clazz,
144: EnclosingMethodAttribute enclosingMethodAttribute) {
145: // Make sure the enclosing class has a name.
146: enclosingMethodAttribute.referencedClassAccept(this );
147: }
148:
149: // Implementations for InnerClassesInfoVisitor.
150:
151: public void visitInnerClassesInfo(Clazz clazz,
152: InnerClassesInfo innerClassesInfo) {
153: // Make sure the outer class has a name, if it exists.
154: int innerClassIndex = innerClassesInfo.u2innerClassIndex;
155: int outerClassIndex = innerClassesInfo.u2outerClassIndex;
156: if (innerClassIndex != 0
157: && outerClassIndex != 0
158: && clazz.getClassName(innerClassIndex).equals(
159: clazz.getName())) {
160: clazz.constantPoolEntryAccept(outerClassIndex, this );
161: }
162: }
163:
164: // Implementations for ConstantVisitor.
165:
166: public void visitClassConstant(Clazz clazz,
167: ClassConstant classConstant) {
168: // Make sure the outer class has a name.
169: classConstant.referencedClassAccept(this );
170: }
171:
172: /**
173: * This ClassVisitor collects package names and class names that have to
174: * be kept.
175: */
176: private class MyKeepCollector implements ClassVisitor {
177: public void visitProgramClass(ProgramClass programClass) {
178: // Does the class already have a new name?
179: String newClassName = newClassName(programClass);
180: if (newClassName != null) {
181: // Remember not to use this name.
182: classNamesToAvoid.add(mixedCaseClassName(newClassName));
183:
184: // Are we not aggressively repackaging all obfuscated classes?
185: if (repackageClasses == null
186: || !allowAccessModification) {
187: String className = programClass.getName();
188:
189: // Keep the package name for all other classes in the same
190: // package. Do this resursively if we're not doing any
191: // repackaging.
192: mapPackageName(className, newClassName,
193: repackageClasses == null
194: && flattenPackageHierarchy == null);
195: }
196: }
197: }
198:
199: public void visitLibraryClass(LibraryClass libraryClass) {
200: }
201:
202: /**
203: * Makes sure the package name of the given class will always be mapped
204: * consistently with its new name.
205: */
206: private void mapPackageName(String className,
207: String newClassName, boolean recursively) {
208: String packagePrefix = ClassUtil
209: .internalPackagePrefix(className);
210: String newPackagePrefix = ClassUtil
211: .internalPackagePrefix(newClassName);
212:
213: // Put the mapping of this package prefix, and possibly of its
214: // entire hierarchy, into the package prefix map.
215: do {
216: packagePrefixMap.put(packagePrefix, newPackagePrefix);
217:
218: if (!recursively) {
219: break;
220: }
221:
222: packagePrefix = ClassUtil
223: .internalPackagePrefix(packagePrefix);
224: newPackagePrefix = ClassUtil
225: .internalPackagePrefix(newPackagePrefix);
226: } while (packagePrefix.length() > 0
227: && newPackagePrefix.length() > 0);
228: }
229: }
230:
231: // Small utility methods.
232:
233: /**
234: * Finds or creates the new package prefix for the given package.
235: */
236: private String newPackagePrefix(String packagePrefix) {
237: // Doesn't the package prefix have a new package prefix yet?
238: String newPackagePrefix = (String) packagePrefixMap
239: .get(packagePrefix);
240: if (newPackagePrefix == null) {
241: // Are we forcing a new package prefix?
242: if (repackageClasses != null) {
243: return repackageClasses;
244: }
245:
246: // Are we forcing a new superpackage prefix?
247: // Othewrise figure out the new superpackage prefix, recursively.
248: String newSuperPackagePrefix = flattenPackageHierarchy != null ? flattenPackageHierarchy
249: : newPackagePrefix(ClassUtil
250: .internalPackagePrefix(packagePrefix));
251:
252: // Come up with a new package prefix.
253: newPackagePrefix = generateUniquePackagePrefix(newSuperPackagePrefix);
254:
255: // Remember to use this mapping in the future.
256: packagePrefixMap.put(packagePrefix, newPackagePrefix);
257: }
258:
259: return newPackagePrefix;
260: }
261:
262: /**
263: * Creates a new package prefix in the given new superpackage.
264: */
265: private String generateUniquePackagePrefix(
266: String newSuperPackagePrefix) {
267: // Find the right name factory for this package.
268: NameFactory packageNameFactory = (NameFactory) packagePrefixPackageNameFactoryMap
269: .get(newSuperPackagePrefix);
270: if (packageNameFactory == null) {
271: // We haven't seen packages in this superpackage before. Create
272: // a new name factory for them.
273: packageNameFactory = new SimpleNameFactory(
274: useMixedCaseClassNames);
275: packagePrefixPackageNameFactoryMap.put(
276: newSuperPackagePrefix, packageNameFactory);
277: }
278:
279: // Come up with package names until we get an original one.
280: String newPackagePrefix;
281: do {
282: // Let the factory produce a package name.
283: newPackagePrefix = newSuperPackagePrefix
284: + packageNameFactory.nextName()
285: + ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
286: } while (packagePrefixMap.containsValue(newPackagePrefix));
287:
288: return newPackagePrefix;
289: }
290:
291: /**
292: * Creates a new class name in the given new package.
293: */
294: private String generateUniqueClassName(String newPackagePrefix) {
295: // Find the right name factory for this package.
296: NameFactory classNameFactory = (NameFactory) packagePrefixClassNameFactoryMap
297: .get(newPackagePrefix);
298: if (classNameFactory == null) {
299: // We haven't seen classes in this package before. Create a new name
300: // factory for them.
301: classNameFactory = new SimpleNameFactory(
302: useMixedCaseClassNames);
303: packagePrefixClassNameFactoryMap.put(newPackagePrefix,
304: classNameFactory);
305: }
306:
307: // Come up with class names until we get an original one.
308: String newClassName;
309: do {
310: // Let the factory produce a class name.
311: newClassName = newPackagePrefix
312: + classNameFactory.nextName();
313: } while (classNamesToAvoid
314: .contains(mixedCaseClassName(newClassName)));
315:
316: return newClassName;
317: }
318:
319: /**
320: * Returns the given class name, unchanged if mixed-case class names are
321: * allowed, or the lower-case version otherwise.
322: */
323: private String mixedCaseClassName(String className) {
324: return useMixedCaseClassNames ? className : className
325: .toLowerCase();
326: }
327:
328: /**
329: * Assigns a new name to the given class.
330: * @param clazz the given class.
331: * @param name the new name.
332: */
333: static void setNewClassName(Clazz clazz, String name) {
334: clazz.setVisitorInfo(name);
335: }
336:
337: /**
338: * Retrieves the new name of the given class.
339: * @param clazz the given class.
340: * @return the class's new name, or <code>null</code> if it doesn't
341: * have one yet.
342: */
343: static String newClassName(Clazz clazz) {
344: Object visitorInfo = clazz.getVisitorInfo();
345:
346: return visitorInfo instanceof String ? (String) visitorInfo
347: : null;
348: }
349: }
|