1/* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2013 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.optimize.peephole; 22 23import proguard.classfile.*; 24import proguard.classfile.attribute.visitor.AttributeNameFilter; 25import proguard.classfile.constant.visitor.*; 26import proguard.classfile.editor.*; 27import proguard.classfile.util.*; 28import proguard.classfile.visitor.*; 29import proguard.optimize.KeepMarker; 30import proguard.optimize.info.*; 31import proguard.util.*; 32 33import java.util.*; 34 35/** 36 * This ClassVisitor inlines the classes that it visits in a given target class, 37 * whenever possible. 38 * 39 * @see RetargetedInnerClassAttributeRemover 40 * @see TargetClassChanger 41 * @see ClassReferenceFixer 42 * @see MemberReferenceFixer 43 * @see AccessFixer 44 * @author Eric Lafortune 45 */ 46public class ClassMerger 47extends SimplifiedVisitor 48implements ClassVisitor, 49 ConstantVisitor 50{ 51 //* 52 private static final boolean DEBUG = false; 53 /*/ 54 private static boolean DEBUG = System.getProperty("cm") != null; 55 //*/ 56 57 58 private final ProgramClass targetClass; 59 private final boolean allowAccessModification; 60 private final boolean mergeInterfacesAggressively; 61 private final ClassVisitor extraClassVisitor; 62 63 private final MemberVisitor fieldOptimizationInfoCopier = new FieldOptimizationInfoCopier(); 64 65 66 /** 67 * Creates a new ClassMerger that will merge classes into the given target 68 * class. 69 * @param targetClass the class into which all visited 70 * classes will be merged. 71 * @param allowAccessModification specifies whether the access modifiers 72 * of classes can be changed in order to 73 * merge them. 74 * @param mergeInterfacesAggressively specifies whether interfaces may 75 * be merged aggressively. 76 */ 77 public ClassMerger(ProgramClass targetClass, 78 boolean allowAccessModification, 79 boolean mergeInterfacesAggressively) 80 { 81 this(targetClass, allowAccessModification, mergeInterfacesAggressively, null); 82 } 83 84 85 /** 86 * Creates a new ClassMerger that will merge classes into the given target 87 * class. 88 * @param targetClass the class into which all visited 89 * classes will be merged. 90 * @param allowAccessModification specifies whether the access modifiers 91 * of classes can be changed in order to 92 * merge them. 93 * @param mergeInterfacesAggressively specifies whether interfaces may 94 * be merged aggressively. 95 * @param extraClassVisitor an optional extra visitor for all 96 * merged classes. 97 */ 98 public ClassMerger(ProgramClass targetClass, 99 boolean allowAccessModification, 100 boolean mergeInterfacesAggressively, 101 ClassVisitor extraClassVisitor) 102 { 103 this.targetClass = targetClass; 104 this.allowAccessModification = allowAccessModification; 105 this.mergeInterfacesAggressively = mergeInterfacesAggressively; 106 this.extraClassVisitor = extraClassVisitor; 107 } 108 109 110 // Implementations for ClassVisitor. 111 112 public void visitProgramClass(ProgramClass programClass) 113 { 114 //final String CLASS_NAME = "abc/Def"; 115 //DEBUG = programClass.getName().equals(CLASS_NAME) || 116 // targetClass.getName().equals(CLASS_NAME); 117 118 // TODO: Remove this when the class merger has stabilized. 119 // Catch any unexpected exceptions from the actual visiting method. 120 try 121 { 122 visitProgramClass0(programClass); 123 } 124 catch (RuntimeException ex) 125 { 126 System.err.println("Unexpected error while merging classes:"); 127 System.err.println(" Class = ["+programClass.getName()+"]"); 128 System.err.println(" Target class = ["+targetClass.getName()+"]"); 129 System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); 130 131 if (DEBUG) 132 { 133 programClass.accept(new ClassPrinter()); 134 targetClass.accept(new ClassPrinter()); 135 } 136 137 throw ex; 138 } 139 } 140 141 public void visitProgramClass0(ProgramClass programClass) 142 { 143 if (!programClass.equals(targetClass) && 144 145 // Don't merge classes that must be preserved. 146 !KeepMarker.isKept(programClass) && 147 !KeepMarker.isKept(targetClass) && 148 149 // Only merge classes that haven't been retargeted yet. 150 getTargetClass(programClass) == null && 151 getTargetClass(targetClass) == null && 152 153 // Don't merge annotation classes, with all their introspection and 154 // infinite recursion. 155 (programClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_ANNOTATTION) == 0 && 156 157 // Only merge classes if we can change the access permissions, or 158 // if they are in the same package, or 159 // if they are public and don't contain or invoke package visible 160 // class members. 161 (allowAccessModification || 162 ((programClass.getAccessFlags() & 163 targetClass.getAccessFlags() & 164 ClassConstants.INTERNAL_ACC_PUBLIC) != 0 && 165 !PackageVisibleMemberContainingClassMarker.containsPackageVisibleMembers(programClass) && 166 !PackageVisibleMemberInvokingClassMarker.invokesPackageVisibleMembers(programClass)) || 167 ClassUtil.internalPackageName(programClass.getName()).equals( 168 ClassUtil.internalPackageName(targetClass.getName()))) && 169 170 // Only merge two classes or two interfaces or two abstract classes, 171 // or a class into an interface with a single implementation. 172 ((programClass.getAccessFlags() & 173 (ClassConstants.INTERNAL_ACC_INTERFACE | 174 ClassConstants.INTERNAL_ACC_ABSTRACT)) == 175 (targetClass.getAccessFlags() & 176 (ClassConstants.INTERNAL_ACC_INTERFACE | 177 ClassConstants.INTERNAL_ACC_ABSTRACT)) || 178 (isOnlySubClass(programClass, targetClass) && 179 (programClass.getSuperClass().equals(targetClass) || 180 programClass.getSuperClass().equals(targetClass.getSuperClass())))) && 181 182 // One class must not implement the other class indirectly. 183 !indirectlyImplementedInterfaces(programClass).contains(targetClass) && 184 !targetClass.extendsOrImplements(programClass) && 185 186 // The two classes must have the same superclasses and interfaces 187 // with static initializers. 188 initializedSuperClasses(programClass).equals(initializedSuperClasses(targetClass)) && 189 190 // The two classes must have the same superclasses and interfaces 191 // that are tested with 'instanceof'. 192 instanceofedSuperClasses(programClass).equals(instanceofedSuperClasses(targetClass)) && 193 194 // The two classes must have the same superclasses that are caught 195 // as exceptions. 196 caughtSuperClasses(programClass).equals(caughtSuperClasses(targetClass)) && 197 198 // The two classes must not both be part of a .class construct. 199 !(DotClassMarker.isDotClassed(programClass) && 200 DotClassMarker.isDotClassed(targetClass)) && 201 202 // The classes must not have clashing fields. 203 !haveAnyIdenticalFields(programClass, targetClass) && 204 205 // The two classes must not introduce any unwanted fields. 206 !introducesUnwantedFields(programClass, targetClass) && 207 !introducesUnwantedFields(targetClass, programClass) && 208 209 // The two classes must not shadow each others fields. 210 !shadowsAnyFields(programClass, targetClass) && 211 !shadowsAnyFields(targetClass, programClass) && 212 213 // The classes must not have clashing methods. 214 !haveAnyIdenticalMethods(programClass, targetClass) && 215 216 // The classes must not introduce abstract methods, unless 217 // explicitly allowed. 218 (mergeInterfacesAggressively || 219 (!introducesUnwantedAbstractMethods(programClass, targetClass) && 220 !introducesUnwantedAbstractMethods(targetClass, programClass))) && 221 222 // The classes must not override each others concrete methods. 223 !overridesAnyMethods(programClass, targetClass) && 224 !overridesAnyMethods(targetClass, programClass) && 225 226 // The classes must not shadow each others non-private methods. 227 !shadowsAnyMethods(programClass, targetClass) && 228 !shadowsAnyMethods(targetClass, programClass)) 229 { 230 if (DEBUG) 231 { 232 System.out.println("ClassMerger ["+programClass.getName()+"] -> ["+targetClass.getName()+"]"); 233 System.out.println(" Source interface? ["+((programClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE)!=0)+"]"); 234 System.out.println(" Target interface? ["+((targetClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE)!=0)+"]"); 235 System.out.println(" Source subclasses ["+programClass.subClasses+"]"); 236 System.out.println(" Target subclasses ["+targetClass.subClasses+"]"); 237 System.out.println(" Source superclass ["+programClass.getSuperClass().getName()+"]"); 238 System.out.println(" Target superclass ["+targetClass.getSuperClass().getName()+"]"); 239 240 //System.out.println("=== Before ==="); 241 //programClass.accept(new ClassPrinter()); 242 //targetClass.accept(new ClassPrinter()); 243 } 244 245 // Combine the access flags. 246 int targetAccessFlags = targetClass.getAccessFlags(); 247 int sourceAccessFlags = programClass.getAccessFlags(); 248 249 targetClass.u2accessFlags = 250 ((targetAccessFlags & 251 sourceAccessFlags) & 252 (ClassConstants.INTERNAL_ACC_INTERFACE | 253 ClassConstants.INTERNAL_ACC_ABSTRACT)) | 254 ((targetAccessFlags | 255 sourceAccessFlags) & 256 (ClassConstants.INTERNAL_ACC_PUBLIC | 257 ClassConstants.INTERNAL_ACC_SUPER | 258 ClassConstants.INTERNAL_ACC_ANNOTATTION | 259 ClassConstants.INTERNAL_ACC_ENUM)); 260 261 // Copy over the superclass, unless it's the target class itself. 262 //if (!targetClass.getName().equals(programClass.getSuperName())) 263 //{ 264 // targetClass.u2superClass = 265 // new ConstantAdder(targetClass).addConstant(programClass, programClass.u2superClass); 266 //} 267 268 // Copy over the interfaces that aren't present yet and that 269 // wouldn't cause loops in the class hierarchy. 270 programClass.interfaceConstantsAccept( 271 new ExceptClassConstantFilter(targetClass.getName(), 272 new ImplementedClassConstantFilter(targetClass, 273 new ImplementingClassConstantFilter(targetClass, 274 new InterfaceAdder(targetClass))))); 275 276 // Copy over the class members. 277 MemberAdder memberAdder = 278 new MemberAdder(targetClass, fieldOptimizationInfoCopier); 279 280 programClass.fieldsAccept(memberAdder); 281 programClass.methodsAccept(memberAdder); 282 283 // Copy over the other attributes. 284 programClass.attributesAccept( 285 new AttributeNameFilter(new NotMatcher(new OrMatcher(new OrMatcher( 286 new FixedStringMatcher(ClassConstants.ATTR_SourceFile), 287 new FixedStringMatcher(ClassConstants.ATTR_InnerClasses)), 288 new FixedStringMatcher(ClassConstants.ATTR_EnclosingMethod))), 289 new AttributeAdder(targetClass, true))); 290 291 // Update the optimization information of the target class. 292 ClassOptimizationInfo info = 293 ClassOptimizationInfo.getClassOptimizationInfo(targetClass); 294 if (info != null) 295 { 296 info.merge(ClassOptimizationInfo.getClassOptimizationInfo(programClass)); 297 } 298 299 // Remember to replace the inlined class by the target class. 300 setTargetClass(programClass, targetClass); 301 302 //if (DEBUG) 303 //{ 304 // System.out.println("=== After ===="); 305 // targetClass.accept(new ClassPrinter()); 306 //} 307 308 // Visit the merged class, if required. 309 if (extraClassVisitor != null) 310 { 311 extraClassVisitor.visitProgramClass(programClass); 312 } 313 } 314 } 315 316 317 // Small utility methods. 318 319 /** 320 * Returns whether a given class is the only subclass of another given class. 321 */ 322 private boolean isOnlySubClass(Clazz subClass, 323 ProgramClass clazz) 324 { 325 // TODO: The list of subclasses is not up to date. 326 return clazz.subClasses != null && 327 clazz.subClasses.length == 1 && 328 clazz.subClasses[0].equals(subClass); 329 } 330 331 332 /** 333 * Returns the set of indirectly implemented interfaces. 334 */ 335 private Set indirectlyImplementedInterfaces(Clazz clazz) 336 { 337 Set set = new HashSet(); 338 339 ReferencedClassVisitor referencedInterfaceCollector = 340 new ReferencedClassVisitor( 341 new ClassHierarchyTraveler(false, false, true, false, 342 new ClassCollector(set))); 343 344 // Visit all superclasses and collect their interfaces. 345 clazz.superClassConstantAccept(referencedInterfaceCollector); 346 347 // Visit all interfaces and collect their interfaces. 348 clazz.interfaceConstantsAccept(referencedInterfaceCollector); 349 350 return set; 351 } 352 353 354 /** 355 * Returns the set of superclasses and interfaces that are initialized. 356 */ 357 private Set initializedSuperClasses(Clazz clazz) 358 { 359 Set set = new HashSet(); 360 361 // Visit all superclasses and interfaces, collecting the ones that have 362 // static initializers. 363 clazz.hierarchyAccept(true, true, true, false, 364 new StaticInitializerContainingClassFilter( 365 new ClassCollector(set))); 366 367 return set; 368 } 369 370 371 /** 372 * Returns the set of superclasses and interfaces that are used in 373 * 'instanceof' tests. 374 */ 375 private Set instanceofedSuperClasses(Clazz clazz) 376 { 377 Set set = new HashSet(); 378 379 // Visit all superclasses and interfaces, collecting the ones that are 380 // used in an 'instanceof' test. 381 clazz.hierarchyAccept(true, true, true, false, 382 new InstanceofClassFilter( 383 new ClassCollector(set))); 384 385 return set; 386 } 387 388 389 /** 390 * Returns the set of superclasses that are caught as exceptions. 391 */ 392 private Set caughtSuperClasses(Clazz clazz) 393 { 394 // Don't bother if this isn't an exception at all. 395 if (!clazz.extends_(ClassConstants.INTERNAL_NAME_JAVA_LANG_THROWABLE)) 396 { 397 return Collections.EMPTY_SET; 398 } 399 400 // Visit all superclasses, collecting the ones that are caught 401 // (plus java.lang.Object, in the current implementation). 402 Set set = new HashSet(); 403 404 clazz.hierarchyAccept(true, true, false, false, 405 new CaughtClassFilter( 406 new ClassCollector(set))); 407 408 return set; 409 } 410 411 412 /** 413 * Returns whether the two given classes have class members with the same 414 * name and descriptor. 415 */ 416 private boolean haveAnyIdenticalFields(Clazz clazz, Clazz targetClass) 417 { 418 MemberCounter counter = new MemberCounter(); 419 420 // Visit all fields, counting the with the same name and descriptor in 421 // the target class. 422 clazz.fieldsAccept(new SimilarMemberVisitor(targetClass, true, false, false, false, 423 counter)); 424 425 return counter.getCount() > 0; 426 } 427 428 429 /** 430 * Returns whether the given class would introduce any unwanted fields 431 * in the target class. 432 */ 433 private boolean introducesUnwantedFields(ProgramClass programClass, 434 ProgramClass targetClass) 435 { 436 // It's ok if the target class is never instantiated, without any other 437 // subclasses except for maybe the source class. 438 if (!InstantiationClassMarker.isInstantiated(targetClass) && 439 (targetClass.subClasses == null || 440 isOnlySubClass(programClass, targetClass))) 441 { 442 return false; 443 } 444 445 MemberCounter counter = new MemberCounter(); 446 447 // Count all non-static fields in the the source class. 448 programClass.fieldsAccept(new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_STATIC, 449 counter)); 450 451 return counter.getCount() > 0; 452 } 453 454 455 /** 456 * Returns whether the given class or its subclasses shadow any fields in 457 * the given target class. 458 */ 459 private boolean shadowsAnyFields(Clazz clazz, Clazz targetClass) 460 { 461 MemberCounter counter = new MemberCounter(); 462 463 // Visit all fields, counting the ones that are shadowing non-private 464 // fields in the class hierarchy of the target class. 465 clazz.hierarchyAccept(true, false, false, true, 466 new AllFieldVisitor( 467 new SimilarMemberVisitor(targetClass, true, true, true, false, 468 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 469 counter)))); 470 471 return counter.getCount() > 0; 472 } 473 474 475 /** 476 * Returns whether the two given classes have class members with the same 477 * name and descriptor. 478 */ 479 private boolean haveAnyIdenticalMethods(Clazz clazz, Clazz targetClass) 480 { 481 MemberCounter counter = new MemberCounter(); 482 483 // Visit all non-abstract methods, counting the ones that are also 484 // present in the target class. 485 clazz.methodsAccept(new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_ABSTRACT, 486 new SimilarMemberVisitor(targetClass, true, false, false, false, 487 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_ABSTRACT, 488 counter)))); 489 490 return counter.getCount() > 0; 491 } 492 493 494 /** 495 * Returns whether the given class would introduce any abstract methods 496 * in the target class. 497 */ 498 private boolean introducesUnwantedAbstractMethods(Clazz clazz, 499 ProgramClass targetClass) 500 { 501 // It's ok if the target class is already abstract and it has at most 502 // the class as a subclass. 503 if ((targetClass.getAccessFlags() & 504 (ClassConstants.INTERNAL_ACC_ABSTRACT | 505 ClassConstants.INTERNAL_ACC_INTERFACE)) != 0 && 506 (targetClass.subClasses == null || 507 isOnlySubClass(clazz, targetClass))) 508 { 509 return false; 510 } 511 512 MemberCounter counter = new MemberCounter(); 513 Set targetSet = new HashSet(); 514 515 // Collect all abstract methods, and similar abstract methods in the 516 // class hierarchy of the target class. 517 clazz.methodsAccept(new MemberAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0, 518 new MultiMemberVisitor(new MemberVisitor[] 519 { 520 counter, 521 new SimilarMemberVisitor(targetClass, true, true, true, false, 522 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0, 523 new MemberCollector(targetSet))) 524 }))); 525 526 return targetSet.size() < counter.getCount(); 527 } 528 529 530 /** 531 * Returns whether the given class overrides any methods in the given 532 * target class. 533 */ 534 private boolean overridesAnyMethods(Clazz clazz, Clazz targetClass) 535 { 536 MemberCounter counter = new MemberCounter(); 537 538 // Visit all non-private non-static methods, counting the ones that are 539 // being overridden in the class hierarchy of the target class. 540 clazz.methodsAccept(new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT, 541 new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_CLINIT)), 542 new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT)), 543 new SimilarMemberVisitor(targetClass, true, true, false, false, 544 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT, 545 counter)))))); 546 547 return counter.getCount() > 0; 548 } 549 550 551 /** 552 * Returns whether the given class or its subclasses shadow any methods in 553 * the given target class. 554 */ 555 private boolean shadowsAnyMethods(Clazz clazz, Clazz targetClass) 556 { 557 MemberCounter counter = new MemberCounter(); 558 559 // Visit all private methods, counting the ones that are shadowing 560 // non-private methods in the class hierarchy of the target class. 561 clazz.hierarchyAccept(true, false, false, true, 562 new AllMethodVisitor( 563 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, 564 new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT)), 565 new SimilarMemberVisitor(targetClass, true, true, true, false, 566 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 567 counter)))))); 568 569 // Visit all static methods, counting the ones that are shadowing 570 // non-private methods in the class hierarchy of the target class. 571 clazz.hierarchyAccept(true, false, false, true, 572 new AllMethodVisitor( 573 new MemberAccessFilter(ClassConstants.INTERNAL_ACC_STATIC, 0, 574 new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_CLINIT)), 575 new SimilarMemberVisitor(targetClass, true, true, true, false, 576 new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, 577 counter)))))); 578 579 return counter.getCount() > 0; 580 } 581 582 583 public static void setTargetClass(Clazz clazz, Clazz targetClass) 584 { 585 ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz); 586 if (info != null) 587 { 588 info.setTargetClass(targetClass); 589 } 590 } 591 592 593 public static Clazz getTargetClass(Clazz clazz) 594 { 595 Clazz targetClass = null; 596 597 // Return the last target class, if any. 598 while (true) 599 { 600 ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz); 601 if (info == null) 602 { 603 return targetClass; 604 } 605 606 clazz = info.getTargetClass(); 607 if (clazz == null) 608 { 609 return targetClass; 610 } 611 612 targetClass = clazz; 613 } 614 } 615 616 617 /** 618 * This MemberVisitor copies field optimization info from copied fields. 619 */ 620 private static class FieldOptimizationInfoCopier 621 extends SimplifiedVisitor 622 implements MemberVisitor 623 { 624 public void visitProgramField(ProgramClass programClass, ProgramField programField) 625 { 626 // Copy the optimization info from the field that was just copied. 627 ProgramField copiedField = (ProgramField)programField.getVisitorInfo(); 628 Object info = copiedField.getVisitorInfo(); 629 630 programField.setVisitorInfo(info instanceof FieldOptimizationInfo ? 631 new FieldOptimizationInfo((FieldOptimizationInfo)info) : 632 info); 633 } 634 635 636 public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) 637 { 638 // Linked methods share their optimization info. 639 } 640 } 641}