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 */ 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.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 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 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 177 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 178 179 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 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 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 */ 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 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 { 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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