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 */ 21 package proguard.obfuscate; 22 23 import proguard.classfile.*; 24 import proguard.classfile.attribute.*; 25 import proguard.classfile.attribute.visitor.*; 26 import proguard.classfile.constant.ClassConstant; 27 import proguard.classfile.constant.visitor.ConstantVisitor; 28 import proguard.classfile.util.*; 29 import proguard.classfile.visitor.ClassVisitor; 30 import proguard.util.*; 31 32 import 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 */ 43 public class ClassObfuscator 44 extends SimplifiedVisitor 45 implements 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 */ ClassObfuscator(ClassPool programClassPool, DictionaryNameFactory classNameFactory, DictionaryNameFactory packageNameFactory, boolean useMixedCaseClassNames, List keepPackageNames, String flattenPackageHierarchy, String repackageClasses, boolean allowAccessModification)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 visitProgramClass(ProgramClass programClass)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 visitAnyAttribute(Clazz clazz, Attribute attribute)169 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 170 171 visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)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 visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)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 visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)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 */ isNumericClassName(String innerClassName, String outerClassName)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 visitClassConstant(Clazz clazz, ClassConstant classConstant)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 { visitProgramClass(ProgramClass programClass)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 visitLibraryClass(LibraryClass libraryClass)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 */ mapPackageName(String className, String newClassName, boolean recursively)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 */ newPackagePrefix(String packagePrefix)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 */ generateUniquePackagePrefix(String newSuperPackagePrefix)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 */ generateUniquePackagePrefix(String newSuperPackagePrefix, NameFactory packageNameFactory)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 */ generateUniqueClassName(String newPackagePrefix)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 */ generateUniqueNumericClassName(String newPackagePrefix)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 */ generateUniqueClassName(String newPackagePrefix, NameFactory classNameFactory)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 */ mixedCaseClassName(String className)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 */ setNewClassName(Clazz clazz, String name)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 */ newClassName(Clazz clazz)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