1 /* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2013 Eric Lafortune (eric (at) 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.*; 24 import proguard.classfile.*; 25 import proguard.classfile.attribute.visitor.*; 26 import proguard.classfile.constant.visitor.AllConstantVisitor; 27 import proguard.classfile.editor.*; 28 import proguard.classfile.util.*; 29 import proguard.classfile.visitor.*; 30 import proguard.util.*; 31 32 import java.io.*; 33 import 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 */ 41 public 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 AttributeVisitor attributeUsageMarker = 103 new NonEmptyAttributeFilter( 104 new AttributeUsageMarker()); 105 106 AttributeVisitor optionalAttributeUsageMarker = 107 configuration.keepAttributes == null ? null : 108 new AttributeNameFilter(new ListParser(new NameParser()).parse(configuration.keepAttributes), 109 attributeUsageMarker); 110 111 programClassPool.classesAccept( 112 new AllAttributeVisitor(true, 113 new RequiredAttributeFilter(attributeUsageMarker, 114 optionalAttributeUsageMarker))); 115 116 // Keep parameter names and types if specified. 117 if (configuration.keepParameterNames) 118 { 119 programClassPool.classesAccept( 120 new AllMethodVisitor( 121 new MemberNameFilter( 122 new AllAttributeVisitor(true, 123 new ParameterNameMarker(attributeUsageMarker))))); 124 } 125 126 // Remove the attributes that can be discarded. Note that the attributes 127 // may only be discarded after the seeds have been marked, since the 128 // configuration may rely on annotations. 129 programClassPool.classesAccept(new AttributeShrinker()); 130 131 // Apply the mapping, if one has been specified. The mapping can 132 // override the names of library classes and of library class members. 133 if (configuration.applyMapping != null) 134 { 135 WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn); 136 137 MappingReader reader = new MappingReader(configuration.applyMapping); 138 139 MappingProcessor keeper = 140 new MultiMappingProcessor(new MappingProcessor[] 141 { 142 new MappingKeeper(programClassPool, warningPrinter), 143 new MappingKeeper(libraryClassPool, null), 144 }); 145 146 reader.pump(keeper); 147 148 // Print out a summary of the warnings if necessary. 149 int warningCount = warningPrinter.getWarningCount(); 150 if (warningCount > 0) 151 { 152 System.err.println("Warning: there were " + warningCount + 153 " kept classes and class members that were remapped anyway."); 154 System.err.println(" You should adapt your configuration or edit the mapping file."); 155 156 if (!configuration.ignoreWarnings) 157 { 158 System.err.println(" If you are sure this remapping won't hurt, you could try your luck"); 159 System.err.println(" using the '-ignorewarnings' option."); 160 } 161 162 System.err.println(" (http://proguard.sourceforge.net/manual/troubleshooting.html#mappingconflict1)"); 163 164 if (!configuration.ignoreWarnings) 165 { 166 throw new IOException("Please correct the above warnings first."); 167 } 168 } 169 } 170 171 // Come up with new names for all classes. 172 DictionaryNameFactory classNameFactory = configuration.classObfuscationDictionary != null ? 173 new DictionaryNameFactory(configuration.classObfuscationDictionary, null) : 174 null; 175 176 DictionaryNameFactory packageNameFactory = configuration.packageObfuscationDictionary != null ? 177 new DictionaryNameFactory(configuration.packageObfuscationDictionary, null) : 178 null; 179 180 programClassPool.classesAccept( 181 new ClassObfuscator(programClassPool, 182 classNameFactory, 183 packageNameFactory, 184 configuration.useMixedCaseClassNames, 185 configuration.keepPackageNames, 186 configuration.flattenPackageHierarchy, 187 configuration.repackageClasses, 188 configuration.allowAccessModification)); 189 190 // Come up with new names for all class members. 191 NameFactory nameFactory = new SimpleNameFactory(); 192 193 if (configuration.obfuscationDictionary != null) 194 { 195 nameFactory = new DictionaryNameFactory(configuration.obfuscationDictionary, 196 nameFactory); 197 } 198 199 WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn); 200 201 // Maintain a map of names to avoid [descriptor - new name - old name]. 202 Map descriptorMap = new HashMap(); 203 204 // Do the class member names have to be globally unique? 205 if (configuration.useUniqueClassMemberNames) 206 { 207 // Collect all member names in all classes. 208 programClassPool.classesAccept( 209 new AllMemberVisitor( 210 new MemberNameCollector(configuration.overloadAggressively, 211 descriptorMap))); 212 213 // Assign new names to all members in all classes. 214 programClassPool.classesAccept( 215 new AllMemberVisitor( 216 new MemberObfuscator(configuration.overloadAggressively, 217 nameFactory, 218 descriptorMap))); 219 } 220 else 221 { 222 // Come up with new names for all non-private class members. 223 programClassPool.classesAccept( 224 new MultiClassVisitor(new ClassVisitor[] 225 { 226 // Collect all private member names in this class and down 227 // the hierarchy. 228 new ClassHierarchyTraveler(true, false, false, true, 229 new AllMemberVisitor( 230 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 231 new MemberNameCollector(configuration.overloadAggressively, 232 descriptorMap)))), 233 234 // Collect all non-private member names anywhere in the hierarchy. 235 new ClassHierarchyTraveler(true, true, true, true, 236 new AllMemberVisitor( 237 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 238 new MemberNameCollector(configuration.overloadAggressively, 239 descriptorMap)))), 240 241 // Assign new names to all non-private members in this class. 242 new AllMemberVisitor( 243 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 244 new MemberObfuscator(configuration.overloadAggressively, 245 nameFactory, 246 descriptorMap))), 247 248 // Clear the collected names. 249 new MapCleaner(descriptorMap) 250 })); 251 252 // Come up with new names for all private class members. 253 programClassPool.classesAccept( 254 new MultiClassVisitor(new ClassVisitor[] 255 { 256 // Collect all member names in this class. 257 new AllMemberVisitor( 258 new MemberNameCollector(configuration.overloadAggressively, 259 descriptorMap)), 260 261 // Collect all non-private member names higher up the hierarchy. 262 new ClassHierarchyTraveler(false, true, true, false, 263 new AllMemberVisitor( 264 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 265 new MemberNameCollector(configuration.overloadAggressively, 266 descriptorMap)))), 267 268 // Collect all member names from interfaces of abstract 269 // classes down the hierarchy. 270 // Due to an error in the JLS/JVMS, virtual invocations 271 // may end up at a private method otherwise (Sun/Oracle 272 // bugs #6691741 and #6684387, ProGuard bug #3471941, 273 // and ProGuard test #1180). 274 new ClassHierarchyTraveler(false, false, false, true, 275 new ClassAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0, 276 new ClassHierarchyTraveler(false, false, true, false, 277 new AllMemberVisitor( 278 new MemberNameCollector(configuration.overloadAggressively, 279 descriptorMap))))), 280 281 // Assign new names to all private members in this class. 282 new AllMemberVisitor( 283 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 284 new MemberObfuscator(configuration.overloadAggressively, 285 nameFactory, 286 descriptorMap))), 287 288 // Clear the collected names. 289 new MapCleaner(descriptorMap) 290 })); 291 } 292 293 // Some class members may have ended up with conflicting names. 294 // Come up with new, globally unique names for them. 295 NameFactory specialNameFactory = 296 new SpecialNameFactory(new SimpleNameFactory()); 297 298 // Collect a map of special names to avoid 299 // [descriptor - new name - old name]. 300 Map specialDescriptorMap = new HashMap(); 301 302 programClassPool.classesAccept( 303 new AllMemberVisitor( 304 new MemberSpecialNameFilter( 305 new MemberNameCollector(configuration.overloadAggressively, 306 specialDescriptorMap)))); 307 308 libraryClassPool.classesAccept( 309 new AllMemberVisitor( 310 new MemberSpecialNameFilter( 311 new MemberNameCollector(configuration.overloadAggressively, 312 specialDescriptorMap)))); 313 314 // Replace conflicting non-private member names with special names. 315 programClassPool.classesAccept( 316 new MultiClassVisitor(new ClassVisitor[] 317 { 318 // Collect all private member names in this class and down 319 // the hierarchy. 320 new ClassHierarchyTraveler(true, false, false, true, 321 new AllMemberVisitor( 322 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 323 new MemberNameCollector(configuration.overloadAggressively, 324 descriptorMap)))), 325 326 // Collect all non-private member names in this class and 327 // higher up the hierarchy. 328 new ClassHierarchyTraveler(true, true, true, false, 329 new AllMemberVisitor( 330 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 331 new MemberNameCollector(configuration.overloadAggressively, 332 descriptorMap)))), 333 334 // Assign new names to all conflicting non-private members 335 // in this class and higher up the hierarchy. 336 new ClassHierarchyTraveler(true, true, true, false, 337 new AllMemberVisitor( 338 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 339 new MemberNameConflictFixer(configuration.overloadAggressively, 340 descriptorMap, 341 warningPrinter, 342 new MemberObfuscator(configuration.overloadAggressively, 343 specialNameFactory, 344 specialDescriptorMap))))), 345 346 // Clear the collected names. 347 new MapCleaner(descriptorMap) 348 })); 349 350 // Replace conflicting private member names with special names. 351 // This is only possible if those names were kept or mapped. 352 programClassPool.classesAccept( 353 new MultiClassVisitor(new ClassVisitor[] 354 { 355 // Collect all member names in this class. 356 new AllMemberVisitor( 357 new MemberNameCollector(configuration.overloadAggressively, 358 descriptorMap)), 359 360 // Collect all non-private member names higher up the hierarchy. 361 new ClassHierarchyTraveler(false, true, true, false, 362 new AllMemberVisitor( 363 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 364 new MemberNameCollector(configuration.overloadAggressively, 365 descriptorMap)))), 366 367 // Assign new names to all conflicting private members in this 368 // class. 369 new AllMemberVisitor( 370 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 371 new MemberNameConflictFixer(configuration.overloadAggressively, 372 descriptorMap, 373 warningPrinter, 374 new MemberObfuscator(configuration.overloadAggressively, 375 specialNameFactory, 376 specialDescriptorMap)))), 377 378 // Clear the collected names. 379 new MapCleaner(descriptorMap) 380 })); 381 382 // Print out any warnings about member name conflicts. 383 int warningCount = warningPrinter.getWarningCount(); 384 if (warningCount > 0) 385 { 386 System.err.println("Warning: there were " + warningCount + 387 " conflicting class member name mappings."); 388 System.err.println(" Your configuration may be inconsistent."); 389 390 if (!configuration.ignoreWarnings) 391 { 392 System.err.println(" If you are sure the conflicts are harmless,"); 393 System.err.println(" you could try your luck using the '-ignorewarnings' option."); 394 } 395 396 System.err.println(" (http://proguard.sourceforge.net/manual/troubleshooting.html#mappingconflict2)"); 397 398 if (!configuration.ignoreWarnings) 399 { 400 throw new IOException("Please correct the above warnings first."); 401 } 402 } 403 404 // Print out the mapping, if requested. 405 if (configuration.printMapping != null) 406 { 407 PrintStream ps = 408 configuration.printMapping == Configuration.STD_OUT ? System.out : 409 new PrintStream( 410 new BufferedOutputStream( 411 new FileOutputStream(configuration.printMapping))); 412 413 // Print out items that will be removed. 414 programClassPool.classesAcceptAlphabetically(new MappingPrinter(ps)); 415 416 if (ps == System.out) 417 { 418 ps.flush(); 419 } 420 else 421 { 422 ps.close(); 423 } 424 } 425 426 // Actually apply the new names. 427 programClassPool.classesAccept(new ClassRenamer()); 428 libraryClassPool.classesAccept(new ClassRenamer()); 429 430 // Update all references to these new names. 431 programClassPool.classesAccept(new ClassReferenceFixer(false)); 432 libraryClassPool.classesAccept(new ClassReferenceFixer(false)); 433 programClassPool.classesAccept(new MemberReferenceFixer()); 434 435 // Make package visible elements public or protected, if obfuscated 436 // classes are being repackaged aggressively. 437 if (configuration.repackageClasses != null && 438 configuration.allowAccessModification) 439 { 440 programClassPool.classesAccept( 441 new AllConstantVisitor( 442 new AccessFixer())); 443 444 // Fix the access flags of the inner classes information. 445 programClassPool.classesAccept( 446 new AllAttributeVisitor( 447 new AllInnerClassesInfoVisitor( 448 new InnerClassesAccessFixer()))); 449 } 450 451 // Fix the bridge method flags. 452 programClassPool.classesAccept( 453 new AllMethodVisitor( 454 new BridgeMethodFixer())); 455 456 // Rename the source file attributes, if requested. 457 if (configuration.newSourceFileAttribute != null) 458 { 459 programClassPool.classesAccept(new SourceFileRenamer(configuration.newSourceFileAttribute)); 460 } 461 462 // Remove unused constants. 463 programClassPool.classesAccept( 464 new ConstantPoolShrinker()); 465 } 466 } 467