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.*; 24import proguard.classfile.*; 25import proguard.classfile.attribute.visitor.*; 26import proguard.classfile.constant.visitor.AllConstantVisitor; 27import proguard.classfile.editor.*; 28import proguard.classfile.util.*; 29import proguard.classfile.visitor.*; 30import proguard.util.*; 31 32import java.io.*; 33import java.util.*; 34 35/** 36 * This class can perform obfuscation of class pools according to a given 37 * specification. 38 * 39 * @author Eric Lafortune 40 */ 41public class Obfuscator 42{ 43 private final Configuration configuration; 44 45 46 /** 47 * Creates a new Obfuscator. 48 */ 49 public Obfuscator(Configuration configuration) 50 { 51 this.configuration = configuration; 52 } 53 54 55 /** 56 * Performs obfuscation of the given program class pool. 57 */ 58 public void execute(ClassPool programClassPool, 59 ClassPool libraryClassPool) throws IOException 60 { 61 // Check if we have at least some keep commands. 62 if (configuration.keep == null && 63 configuration.applyMapping == null && 64 configuration.printMapping == null) 65 { 66 throw new IOException("You have to specify '-keep' options for the obfuscation step."); 67 } 68 69 // Clean up any old visitor info. 70 programClassPool.classesAccept(new ClassCleaner()); 71 libraryClassPool.classesAccept(new ClassCleaner()); 72 73 // If the class member names have to correspond globally, 74 // link all class members in all classes, otherwise 75 // link all non-private methods in all class hierarchies. 76 ClassVisitor memberInfoLinker = 77 configuration.useUniqueClassMemberNames ? 78 (ClassVisitor)new AllMemberVisitor(new MethodLinker()) : 79 (ClassVisitor)new BottomClassFilter(new MethodLinker()); 80 81 programClassPool.classesAccept(memberInfoLinker); 82 libraryClassPool.classesAccept(memberInfoLinker); 83 84 // Create a visitor for marking the seeds. 85 NameMarker nameMarker = new NameMarker(); 86 ClassPoolVisitor classPoolvisitor = 87 ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep, 88 nameMarker, 89 nameMarker, 90 false, 91 false, 92 true); 93 // Mark the seeds. 94 programClassPool.accept(classPoolvisitor); 95 libraryClassPool.accept(classPoolvisitor); 96 97 // All library classes and library class members keep their names. 98 libraryClassPool.classesAccept(nameMarker); 99 libraryClassPool.classesAccept(new AllMemberVisitor(nameMarker)); 100 101 // Mark attributes that have to be kept. 102 AttributeUsageMarker requiredAttributeUsageMarker = 103 new AttributeUsageMarker(); 104 105 AttributeVisitor optionalAttributeUsageMarker = 106 configuration.keepAttributes == null ? null : 107 new AttributeNameFilter(new ListParser(new NameParser()).parse(configuration.keepAttributes), 108 requiredAttributeUsageMarker); 109 110 programClassPool.classesAccept( 111 new AllAttributeVisitor(true, 112 new RequiredAttributeFilter(requiredAttributeUsageMarker, 113 optionalAttributeUsageMarker))); 114 115 // Remove the attributes that can be discarded. Note that the attributes 116 // may only be discarded after the seeds have been marked, since the 117 // configuration may rely on annotations. 118 programClassPool.classesAccept(new AttributeShrinker()); 119 120 // Apply the mapping, if one has been specified. The mapping can 121 // override the names of library classes and of library class members. 122 if (configuration.applyMapping != null) 123 { 124 WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn); 125 126 MappingReader reader = new MappingReader(configuration.applyMapping); 127 128 MappingProcessor keeper = 129 new MultiMappingProcessor(new MappingProcessor[] 130 { 131 new MappingKeeper(programClassPool, warningPrinter), 132 new MappingKeeper(libraryClassPool, null), 133 }); 134 135 reader.pump(keeper); 136 137 // Print out a summary of the warnings if necessary. 138 int mappingWarningCount = warningPrinter.getWarningCount(); 139 if (mappingWarningCount > 0) 140 { 141 System.err.println("Warning: there were " + mappingWarningCount + 142 " kept classes and class members that were remapped anyway."); 143 System.err.println(" You should adapt your configuration or edit the mapping file."); 144 145 if (!configuration.ignoreWarnings) 146 { 147 System.err.println(" If you are sure this remapping won't hurt,"); 148 System.err.println(" you could try your luck using the '-ignorewarnings' option."); 149 throw new IOException("Please correct the above warnings first."); 150 } 151 } 152 } 153 154 // Come up with new names for all classes. 155 DictionaryNameFactory classNameFactory = configuration.classObfuscationDictionary != null ? 156 new DictionaryNameFactory(configuration.classObfuscationDictionary, null) : 157 null; 158 159 DictionaryNameFactory packageNameFactory = configuration.packageObfuscationDictionary != null ? 160 new DictionaryNameFactory(configuration.packageObfuscationDictionary, null) : 161 null; 162 163 programClassPool.classesAccept( 164 new ClassObfuscator(programClassPool, 165 classNameFactory, 166 packageNameFactory, 167 configuration.useMixedCaseClassNames, 168 configuration.keepPackageNames, 169 configuration.flattenPackageHierarchy, 170 configuration.repackageClasses, 171 configuration.allowAccessModification)); 172 173 // Come up with new names for all class members. 174 NameFactory nameFactory = new SimpleNameFactory(); 175 176 if (configuration.obfuscationDictionary != null) 177 { 178 nameFactory = new DictionaryNameFactory(configuration.obfuscationDictionary, 179 nameFactory); 180 } 181 182 WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn); 183 184 // Maintain a map of names to avoid [descriptor - new name - old name]. 185 Map descriptorMap = new HashMap(); 186 187 // Do the class member names have to be globally unique? 188 if (configuration.useUniqueClassMemberNames) 189 { 190 // Collect all member names in all classes. 191 programClassPool.classesAccept( 192 new AllMemberVisitor( 193 new MemberNameCollector(configuration.overloadAggressively, 194 descriptorMap))); 195 196 // Assign new names to all members in all classes. 197 programClassPool.classesAccept( 198 new AllMemberVisitor( 199 new MemberObfuscator(configuration.overloadAggressively, 200 nameFactory, 201 descriptorMap))); 202 } 203 else 204 { 205 // Come up with new names for all non-private class members. 206 programClassPool.classesAccept( 207 new MultiClassVisitor(new ClassVisitor[] 208 { 209 // Collect all private member names in this class and down 210 // the hierarchy. 211 new ClassHierarchyTraveler(true, false, false, true, 212 new AllMemberVisitor( 213 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 214 new MemberNameCollector(configuration.overloadAggressively, 215 descriptorMap)))), 216 217 // Collect all non-private member names anywhere in the hierarchy. 218 new ClassHierarchyTraveler(true, true, true, true, 219 new AllMemberVisitor( 220 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 221 new MemberNameCollector(configuration.overloadAggressively, 222 descriptorMap)))), 223 224 // Assign new names to all non-private members in this class. 225 new AllMemberVisitor( 226 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 227 new MemberObfuscator(configuration.overloadAggressively, 228 nameFactory, 229 descriptorMap))), 230 231 // Clear the collected names. 232 new MapCleaner(descriptorMap) 233 })); 234 235 // Come up with new names for all private class members. 236 programClassPool.classesAccept( 237 new MultiClassVisitor(new ClassVisitor[] 238 { 239 // Collect all member names in this class. 240 new AllMemberVisitor( 241 new MemberNameCollector(configuration.overloadAggressively, 242 descriptorMap)), 243 244 // Collect all non-private member names higher up the hierarchy. 245 new ClassHierarchyTraveler(false, true, true, false, 246 new AllMemberVisitor( 247 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 248 new MemberNameCollector(configuration.overloadAggressively, 249 descriptorMap)))), 250 251 // Assign new names to all private members in this class. 252 new AllMemberVisitor( 253 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 254 new MemberObfuscator(configuration.overloadAggressively, 255 nameFactory, 256 descriptorMap))), 257 258 // Clear the collected names. 259 new MapCleaner(descriptorMap) 260 })); 261 } 262 263 // Some class members may have ended up with conflicting names. 264 // Come up with new, globally unique names for them. 265 NameFactory specialNameFactory = 266 new SpecialNameFactory(new SimpleNameFactory()); 267 268 // Collect a map of special names to avoid 269 // [descriptor - new name - old name]. 270 Map specialDescriptorMap = new HashMap(); 271 272 programClassPool.classesAccept( 273 new AllMemberVisitor( 274 new MemberSpecialNameFilter( 275 new MemberNameCollector(configuration.overloadAggressively, 276 specialDescriptorMap)))); 277 278 libraryClassPool.classesAccept( 279 new AllMemberVisitor( 280 new MemberSpecialNameFilter( 281 new MemberNameCollector(configuration.overloadAggressively, 282 specialDescriptorMap)))); 283 284 // Replace conflicting non-private member names with special names. 285 programClassPool.classesAccept( 286 new MultiClassVisitor(new ClassVisitor[] 287 { 288 // Collect all private member names in this class and down 289 // the hierarchy. 290 new ClassHierarchyTraveler(true, false, false, true, 291 new AllMemberVisitor( 292 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 293 new MemberNameCollector(configuration.overloadAggressively, 294 descriptorMap)))), 295 296 // Collect all non-private member names in this class and 297 // higher up the hierarchy. 298 new ClassHierarchyTraveler(true, true, true, false, 299 new AllMemberVisitor( 300 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 301 new MemberNameCollector(configuration.overloadAggressively, 302 descriptorMap)))), 303 304 // Assign new names to all conflicting non-private members 305 // in this class and higher up the hierarchy. 306 new ClassHierarchyTraveler(true, true, true, false, 307 new AllMemberVisitor( 308 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 309 new MemberNameConflictFixer(configuration.overloadAggressively, 310 descriptorMap, 311 warningPrinter, 312 new MemberObfuscator(configuration.overloadAggressively, 313 specialNameFactory, 314 specialDescriptorMap))))), 315 316 // Clear the collected names. 317 new MapCleaner(descriptorMap) 318 })); 319 320 // Replace conflicting private member names with special names. 321 // This is only possible if those names were kept or mapped. 322 programClassPool.classesAccept( 323 new MultiClassVisitor(new ClassVisitor[] 324 { 325 // Collect all member names in this class. 326 new AllMemberVisitor( 327 new MemberNameCollector(configuration.overloadAggressively, 328 descriptorMap)), 329 330 // Collect all non-private member names higher up the hierarchy. 331 new ClassHierarchyTraveler(false, true, true, false, 332 new AllMemberVisitor( 333 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 334 new MemberNameCollector(configuration.overloadAggressively, 335 descriptorMap)))), 336 337 // Assign new names to all conflicting private members in this 338 // class. 339 new AllMemberVisitor( 340 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 341 new MemberNameConflictFixer(configuration.overloadAggressively, 342 descriptorMap, 343 warningPrinter, 344 new MemberObfuscator(configuration.overloadAggressively, 345 specialNameFactory, 346 specialDescriptorMap)))), 347 348 // Clear the collected names. 349 new MapCleaner(descriptorMap) 350 })); 351 352 // Print out any warnings about member name conflicts. 353 int warningCount = warningPrinter.getWarningCount(); 354 if (warningCount > 0) 355 { 356 System.err.println("Warning: there were " + warningCount + 357 " conflicting class member name mappings."); 358 System.err.println(" Your configuration may be inconsistent."); 359 360 if (!configuration.ignoreWarnings) 361 { 362 System.err.println(" If you are sure the conflicts are harmless,"); 363 System.err.println(" you could try your luck using the '-ignorewarnings' option."); 364 throw new IOException("Please correct the above warnings first."); 365 } 366 } 367 368 // Print out the mapping, if requested. 369 if (configuration.printMapping != null) 370 { 371 PrintStream ps = isFile(configuration.printMapping) ? 372 new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printMapping))) : 373 System.out; 374 375 // Print out items that will be removed. 376 programClassPool.classesAcceptAlphabetically(new MappingPrinter(ps)); 377 378 if (ps != System.out) 379 { 380 ps.close(); 381 } 382 } 383 384 // Actually apply the new names. 385 programClassPool.classesAccept(new ClassRenamer()); 386 libraryClassPool.classesAccept(new ClassRenamer()); 387 388 // Update all references to these new names. 389 programClassPool.classesAccept(new ClassReferenceFixer(false)); 390 libraryClassPool.classesAccept(new ClassReferenceFixer(false)); 391 programClassPool.classesAccept(new MemberReferenceFixer()); 392 393 // Make package visible elements public or protected, if obfuscated 394 // classes are being repackaged aggressively. 395 if (configuration.repackageClasses != null && 396 configuration.allowAccessModification) 397 { 398 programClassPool.classesAccept( 399 new AllConstantVisitor( 400 new AccessFixer())); 401 } 402 403 // Rename the source file attributes, if requested. 404 if (configuration.newSourceFileAttribute != null) 405 { 406 programClassPool.classesAccept(new SourceFileRenamer(configuration.newSourceFileAttribute)); 407 } 408 409 // Mark NameAndType constant pool entries that have to be kept 410 // and remove the other ones. 411 programClassPool.classesAccept(new NameAndTypeUsageMarker()); 412 programClassPool.classesAccept(new NameAndTypeShrinker()); 413 414 // Mark Utf8 constant pool entries that have to be kept 415 // and remove the other ones. 416 programClassPool.classesAccept(new Utf8UsageMarker()); 417 programClassPool.classesAccept(new Utf8Shrinker()); 418 } 419 420 421 /** 422 * Returns whether the given file is actually a file, or just a placeholder 423 * for the standard output. 424 */ 425 private boolean isFile(File file) 426 { 427 return file.getPath().length() > 0; 428 } 429} 430