1 /* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2014 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.PACKAGE_SEPARATOR; 113 } 114 115 // First append the package separator if necessary. 116 if (repackageClasses != null && 117 repackageClasses.length() > 0) 118 { 119 repackageClasses += ClassConstants.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.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 visitLibraryClass(LibraryClass libraryClass)167 public void visitLibraryClass(LibraryClass libraryClass) 168 { 169 // This can happen for dubious input, if the outer class of a program 170 // class is a library class, and its name is requested. 171 newClassName = libraryClass.getName(); 172 } 173 174 175 // Implementations for AttributeVisitor. 176 visitAnyAttribute(Clazz clazz, Attribute attribute)177 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 178 179 visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)180 public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) 181 { 182 // Make sure the outer classes have a name, if they exist. 183 innerClassesAttribute.innerClassEntriesAccept(clazz, this); 184 } 185 186 visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)187 public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) 188 { 189 // Make sure the enclosing class has a name. 190 enclosingMethodAttribute.referencedClassAccept(this); 191 192 String innerClassName = clazz.getName(); 193 String outerClassName = clazz.getClassName(enclosingMethodAttribute.u2classIndex); 194 195 numericClassName = isNumericClassName(innerClassName, 196 outerClassName); 197 } 198 199 200 // Implementations for InnerClassesInfoVisitor. 201 visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)202 public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) 203 { 204 // Make sure the outer class has a name, if it exists. 205 int innerClassIndex = innerClassesInfo.u2innerClassIndex; 206 int outerClassIndex = innerClassesInfo.u2outerClassIndex; 207 if (innerClassIndex != 0 && 208 outerClassIndex != 0) 209 { 210 String innerClassName = clazz.getClassName(innerClassIndex); 211 if (innerClassName.equals(clazz.getName())) 212 { 213 clazz.constantPoolEntryAccept(outerClassIndex, this); 214 215 String outerClassName = clazz.getClassName(outerClassIndex); 216 217 numericClassName = isNumericClassName(innerClassName, 218 outerClassName); 219 } 220 } 221 } 222 223 224 /** 225 * Returns whether the given inner class name is a numeric name. 226 */ isNumericClassName(String innerClassName, String outerClassName)227 private boolean isNumericClassName(String innerClassName, 228 String outerClassName) 229 { 230 int innerClassNameStart = outerClassName.length() + 1; 231 int innerClassNameLength = innerClassName.length(); 232 233 if (innerClassNameStart >= innerClassNameLength) 234 { 235 return false; 236 } 237 238 for (int index = innerClassNameStart; index < innerClassNameLength; index++) 239 { 240 if (!Character.isDigit(innerClassName.charAt(index))) 241 { 242 return false; 243 } 244 } 245 246 return true; 247 } 248 249 250 // Implementations for ConstantVisitor. 251 visitClassConstant(Clazz clazz, ClassConstant classConstant)252 public void visitClassConstant(Clazz clazz, ClassConstant classConstant) 253 { 254 // Make sure the outer class has a name. 255 classConstant.referencedClassAccept(this); 256 } 257 258 259 /** 260 * This ClassVisitor collects package names and class names that have to 261 * be kept. 262 */ 263 private class MyKeepCollector implements ClassVisitor 264 { visitProgramClass(ProgramClass programClass)265 public void visitProgramClass(ProgramClass programClass) 266 { 267 // Does the class already have a new name? 268 String newClassName = newClassName(programClass); 269 if (newClassName != null) 270 { 271 // Remember not to use this name. 272 classNamesToAvoid.add(mixedCaseClassName(newClassName)); 273 274 // Are we not aggressively repackaging all obfuscated classes? 275 if (repackageClasses == null || 276 !allowAccessModification) 277 { 278 String className = programClass.getName(); 279 280 // Keep the package name for all other classes in the same 281 // package. Do this recursively if we're not doing any 282 // repackaging. 283 mapPackageName(className, 284 newClassName, 285 repackageClasses == null && 286 flattenPackageHierarchy == null); 287 } 288 } 289 } 290 291 visitLibraryClass(LibraryClass libraryClass)292 public void visitLibraryClass(LibraryClass libraryClass) 293 { 294 } 295 296 297 /** 298 * Makes sure the package name of the given class will always be mapped 299 * consistently with its new name. 300 */ mapPackageName(String className, String newClassName, boolean recursively)301 private void mapPackageName(String className, 302 String newClassName, 303 boolean recursively) 304 { 305 String packagePrefix = ClassUtil.internalPackagePrefix(className); 306 String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName); 307 308 // Put the mapping of this package prefix, and possibly of its 309 // entire hierarchy, into the package prefix map. 310 do 311 { 312 packagePrefixMap.put(packagePrefix, newPackagePrefix); 313 314 if (!recursively) 315 { 316 break; 317 } 318 319 packagePrefix = ClassUtil.internalPackagePrefix(packagePrefix); 320 newPackagePrefix = ClassUtil.internalPackagePrefix(newPackagePrefix); 321 } 322 while (packagePrefix.length() > 0 && 323 newPackagePrefix.length() > 0); 324 } 325 } 326 327 328 // Small utility methods. 329 330 /** 331 * Finds or creates the new package prefix for the given package. 332 */ newPackagePrefix(String packagePrefix)333 private String newPackagePrefix(String packagePrefix) 334 { 335 // Doesn't the package prefix have a new package prefix yet? 336 String newPackagePrefix = (String)packagePrefixMap.get(packagePrefix); 337 if (newPackagePrefix == null) 338 { 339 // Are we keeping the package name? 340 if (keepPackageNamesMatcher != null && 341 keepPackageNamesMatcher.matches(packagePrefix.length() > 0 ? 342 packagePrefix.substring(0, packagePrefix.length()-1) : 343 packagePrefix)) 344 { 345 return packagePrefix; 346 } 347 348 // Are we forcing a new package prefix? 349 if (repackageClasses != null) 350 { 351 return repackageClasses; 352 } 353 354 // Are we forcing a new superpackage prefix? 355 // Otherwise figure out the new superpackage prefix, recursively. 356 String newSuperPackagePrefix = flattenPackageHierarchy != null ? 357 flattenPackageHierarchy : 358 newPackagePrefix(ClassUtil.internalPackagePrefix(packagePrefix)); 359 360 // Come up with a new package prefix. 361 newPackagePrefix = generateUniquePackagePrefix(newSuperPackagePrefix); 362 363 // Remember to use this mapping in the future. 364 packagePrefixMap.put(packagePrefix, newPackagePrefix); 365 } 366 367 return newPackagePrefix; 368 } 369 370 371 /** 372 * Creates a new package prefix in the given new superpackage. 373 */ generateUniquePackagePrefix(String newSuperPackagePrefix)374 private String generateUniquePackagePrefix(String newSuperPackagePrefix) 375 { 376 // Find the right name factory for this package. 377 NameFactory packageNameFactory = 378 (NameFactory)packagePrefixPackageNameFactoryMap.get(newSuperPackagePrefix); 379 if (packageNameFactory == null) 380 { 381 // We haven't seen packages in this superpackage before. Create 382 // a new name factory for them. 383 packageNameFactory = new SimpleNameFactory(useMixedCaseClassNames); 384 if (this.packageNameFactory != null) 385 { 386 packageNameFactory = 387 new DictionaryNameFactory(this.packageNameFactory, 388 packageNameFactory); 389 } 390 391 packagePrefixPackageNameFactoryMap.put(newSuperPackagePrefix, 392 packageNameFactory); 393 } 394 395 return generateUniquePackagePrefix(newSuperPackagePrefix, packageNameFactory); 396 } 397 398 399 /** 400 * Creates a new package prefix in the given new superpackage, with the 401 * given package name factory. 402 */ generateUniquePackagePrefix(String newSuperPackagePrefix, NameFactory packageNameFactory)403 private String generateUniquePackagePrefix(String newSuperPackagePrefix, 404 NameFactory packageNameFactory) 405 { 406 // Come up with package names until we get an original one. 407 String newPackagePrefix; 408 do 409 { 410 // Let the factory produce a package name. 411 newPackagePrefix = newSuperPackagePrefix + 412 packageNameFactory.nextName() + 413 ClassConstants.PACKAGE_SEPARATOR; 414 } 415 while (packagePrefixMap.containsValue(newPackagePrefix)); 416 417 return newPackagePrefix; 418 } 419 420 421 /** 422 * Creates a new class name in the given new package. 423 */ generateUniqueClassName(String newPackagePrefix)424 private String generateUniqueClassName(String newPackagePrefix) 425 { 426 // Find the right name factory for this package. 427 NameFactory classNameFactory = 428 (NameFactory)packagePrefixClassNameFactoryMap.get(newPackagePrefix); 429 if (classNameFactory == null) 430 { 431 // We haven't seen classes in this package before. 432 // Create a new name factory for them. 433 classNameFactory = new SimpleNameFactory(useMixedCaseClassNames); 434 if (this.classNameFactory != null) 435 { 436 classNameFactory = 437 new DictionaryNameFactory(this.classNameFactory, 438 classNameFactory); 439 } 440 441 packagePrefixClassNameFactoryMap.put(newPackagePrefix, 442 classNameFactory); 443 } 444 445 return generateUniqueClassName(newPackagePrefix, classNameFactory); 446 } 447 448 449 /** 450 * Creates a new class name in the given new package. 451 */ generateUniqueNumericClassName(String newPackagePrefix)452 private String generateUniqueNumericClassName(String newPackagePrefix) 453 { 454 // Find the right name factory for this package. 455 NameFactory classNameFactory = 456 (NameFactory)packagePrefixNumericClassNameFactoryMap.get(newPackagePrefix); 457 if (classNameFactory == null) 458 { 459 // We haven't seen classes in this package before. 460 // Create a new name factory for them. 461 classNameFactory = new NumericNameFactory(); 462 463 packagePrefixNumericClassNameFactoryMap.put(newPackagePrefix, 464 classNameFactory); 465 } 466 467 return generateUniqueClassName(newPackagePrefix, classNameFactory); 468 } 469 470 471 /** 472 * Creates a new class name in the given new package, with the given 473 * class name factory. 474 */ generateUniqueClassName(String newPackagePrefix, NameFactory classNameFactory)475 private String generateUniqueClassName(String newPackagePrefix, 476 NameFactory classNameFactory) 477 { 478 // Come up with class names until we get an original one. 479 String newClassName; 480 String newMixedCaseClassName; 481 do 482 { 483 // Let the factory produce a class name. 484 newClassName = newPackagePrefix + 485 classNameFactory.nextName(); 486 487 newMixedCaseClassName = mixedCaseClassName(newClassName); 488 } 489 while (classNamesToAvoid.contains(newMixedCaseClassName)); 490 491 // Explicitly make sure the name isn't used again if we have a 492 // user-specified dictionary and we're not allowed to have mixed case 493 // class names -- just to protect against problematic dictionaries. 494 if (this.classNameFactory != null && 495 !useMixedCaseClassNames) 496 { 497 classNamesToAvoid.add(newMixedCaseClassName); 498 } 499 500 return newClassName; 501 } 502 503 504 /** 505 * Returns the given class name, unchanged if mixed-case class names are 506 * allowed, or the lower-case version otherwise. 507 */ mixedCaseClassName(String className)508 private String mixedCaseClassName(String className) 509 { 510 return useMixedCaseClassNames ? 511 className : 512 className.toLowerCase(); 513 } 514 515 516 /** 517 * Assigns a new name to the given class. 518 * @param clazz the given class. 519 * @param name the new name. 520 */ setNewClassName(Clazz clazz, String name)521 static void setNewClassName(Clazz clazz, String name) 522 { 523 clazz.setVisitorInfo(name); 524 } 525 526 527 /** 528 * Retrieves the new name of the given class. 529 * @param clazz the given class. 530 * @return the class's new name, or <code>null</code> if it doesn't 531 * have one yet. 532 */ newClassName(Clazz clazz)533 static String newClassName(Clazz clazz) 534 { 535 Object visitorInfo = clazz.getVisitorInfo(); 536 537 return visitorInfo instanceof String ? 538 (String)visitorInfo : 539 null; 540 } 541 } 542