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