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