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.classfile.editor; 22 23import proguard.classfile.*; 24import proguard.classfile.attribute.*; 25import proguard.classfile.attribute.annotation.*; 26import proguard.classfile.attribute.annotation.visitor.*; 27import proguard.classfile.attribute.visitor.*; 28import proguard.classfile.constant.*; 29import proguard.classfile.constant.visitor.ConstantVisitor; 30import proguard.classfile.util.*; 31import proguard.classfile.visitor.*; 32 33/** 34 * This ClassVisitor fixes references of constant pool entries, fields, 35 * methods, and attributes to classes whose names have changed. Descriptors 36 * of member references are not updated yet. 37 * 38 * @see MemberReferenceFixer 39 * @author Eric Lafortune 40 */ 41public class ClassReferenceFixer 42extends SimplifiedVisitor 43implements ClassVisitor, 44 ConstantVisitor, 45 MemberVisitor, 46 AttributeVisitor, 47 InnerClassesInfoVisitor, 48 LocalVariableInfoVisitor, 49 LocalVariableTypeInfoVisitor, 50 AnnotationVisitor, 51 ElementValueVisitor 52{ 53 private final boolean ensureUniqueMemberNames; 54 55 56 /** 57 * Creates a new ClassReferenceFixer. 58 * @param ensureUniqueMemberNames specifies whether class members whose 59 * descriptor changes should get new, unique 60 * names, in order to avoid naming conflicts 61 * with similar methods. 62 */ 63 public ClassReferenceFixer(boolean ensureUniqueMemberNames) 64 { 65 this.ensureUniqueMemberNames = ensureUniqueMemberNames; 66 } 67 68 69 // Implementations for ClassVisitor. 70 71 public void visitProgramClass(ProgramClass programClass) 72 { 73 // Fix the constant pool. 74 programClass.constantPoolEntriesAccept(this); 75 76 // Fix class members. 77 programClass.fieldsAccept(this); 78 programClass.methodsAccept(this); 79 80 // Fix the attributes. 81 programClass.attributesAccept(this); 82 } 83 84 85 public void visitLibraryClass(LibraryClass libraryClass) 86 { 87 // Fix class members. 88 libraryClass.fieldsAccept(this); 89 libraryClass.methodsAccept(this); 90 } 91 92 93 // Implementations for MemberVisitor. 94 95 public void visitProgramField(ProgramClass programClass, ProgramField programField) 96 { 97 // Has the descriptor changed? 98 String descriptor = programField.getDescriptor(programClass); 99 String newDescriptor = newDescriptor(descriptor, 100 programField.referencedClass); 101 102 if (!descriptor.equals(newDescriptor)) 103 { 104 ConstantPoolEditor constantPoolEditor = 105 new ConstantPoolEditor(programClass); 106 107 // Update the descriptor. 108 programField.u2descriptorIndex = 109 constantPoolEditor.addUtf8Constant(newDescriptor); 110 111 // Update the name, if requested. 112 if (ensureUniqueMemberNames) 113 { 114 String name = programField.getName(programClass); 115 String newName = newUniqueMemberName(name, descriptor); 116 programField.u2nameIndex = 117 constantPoolEditor.addUtf8Constant(newName); 118 } 119 } 120 121 // Fix the attributes. 122 programField.attributesAccept(programClass, this); 123 } 124 125 126 public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) 127 { 128 // Has the descriptor changed? 129 String descriptor = programMethod.getDescriptor(programClass); 130 String newDescriptor = newDescriptor(descriptor, 131 programMethod.referencedClasses); 132 133 if (!descriptor.equals(newDescriptor)) 134 { 135 ConstantPoolEditor constantPoolEditor = 136 new ConstantPoolEditor(programClass); 137 138 // Update the descriptor. 139 programMethod.u2descriptorIndex = 140 constantPoolEditor.addUtf8Constant(newDescriptor); 141 142 // Update the name, if requested. 143 if (ensureUniqueMemberNames) 144 { 145 String name = programMethod.getName(programClass); 146 String newName = newUniqueMemberName(name, descriptor); 147 programMethod.u2nameIndex = 148 constantPoolEditor.addUtf8Constant(newName); 149 } 150 } 151 152 // Fix the attributes. 153 programMethod.attributesAccept(programClass, this); 154 } 155 156 157 public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) 158 { 159 // Has the descriptor changed? 160 String descriptor = libraryField.getDescriptor(libraryClass); 161 String newDescriptor = newDescriptor(descriptor, 162 libraryField.referencedClass); 163 164 // Update the descriptor. 165 libraryField.descriptor = newDescriptor; 166 } 167 168 169 public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) 170 { 171 // Has the descriptor changed? 172 String descriptor = libraryMethod.getDescriptor(libraryClass); 173 String newDescriptor = newDescriptor(descriptor, 174 libraryMethod.referencedClasses); 175 176 if (!descriptor.equals(newDescriptor)) 177 { 178 // Update the descriptor. 179 libraryMethod.descriptor = newDescriptor; 180 } 181 } 182 183 184 // Implementations for ConstantVisitor. 185 186 public void visitAnyConstant(Clazz clazz, Constant constant) {} 187 188 189 public void visitStringConstant(Clazz clazz, StringConstant stringConstant) 190 { 191 // Does the string refer to a class, due to a Class.forName construct? 192 Clazz referencedClass = stringConstant.referencedClass; 193 Member referencedMember = stringConstant.referencedMember; 194 if (referencedClass != null && 195 referencedMember == null) 196 { 197 // Reconstruct the new class name. 198 String externalClassName = stringConstant.getString(clazz); 199 String internalClassName = ClassUtil.internalClassName(externalClassName); 200 String newInternalClassName = newClassName(internalClassName, 201 referencedClass); 202 203 // Update the String entry if required. 204 if (!newInternalClassName.equals(internalClassName)) 205 { 206 // Only convert to an external class name if the original was 207 // an external class name too. 208 String newExternalClassName = 209 externalClassName.indexOf(JavaConstants.PACKAGE_SEPARATOR) >= 0 ? 210 ClassUtil.externalClassName(newInternalClassName) : 211 newInternalClassName; 212 213 // Refer to a new Utf8 entry. 214 stringConstant.u2stringIndex = 215 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newExternalClassName); 216 } 217 } 218 } 219 220 221 public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) 222 { 223 // Has the descriptor changed? 224 String descriptor = invokeDynamicConstant.getType(clazz); 225 String newDescriptor = newDescriptor(descriptor, 226 invokeDynamicConstant.referencedClasses); 227 228 if (!descriptor.equals(newDescriptor)) 229 { 230 String name = invokeDynamicConstant.getName(clazz); 231 232 // Refer to a new NameAndType entry. 233 invokeDynamicConstant.u2nameAndTypeIndex = 234 new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(name, newDescriptor); 235 } 236 } 237 238 239 public void visitClassConstant(Clazz clazz, ClassConstant classConstant) 240 { 241 // Do we know the referenced class? 242 Clazz referencedClass = classConstant.referencedClass; 243 if (referencedClass != null) 244 { 245 // Has the class name changed? 246 String className = classConstant.getName(clazz); 247 String newClassName = newClassName(className, referencedClass); 248 if (!className.equals(newClassName)) 249 { 250 // Refer to a new Utf8 entry. 251 classConstant.u2nameIndex = 252 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName); 253 } 254 } 255 } 256 257 258 public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) 259 { 260 // Has the descriptor changed? 261 String descriptor = methodTypeConstant.getType(clazz); 262 String newDescriptor = newDescriptor(descriptor, 263 methodTypeConstant.referencedClasses); 264 265 if (!descriptor.equals(newDescriptor)) 266 { 267 // Update the descriptor. 268 methodTypeConstant.u2descriptorIndex = 269 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor); 270 } 271 } 272 273 274 // Implementations for AttributeVisitor. 275 276 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 277 278 279 public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) 280 { 281 // Fix the inner class names. 282 innerClassesAttribute.innerClassEntriesAccept(clazz, this); 283 } 284 285 286 public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 287 { 288 // Fix the attributes. 289 codeAttribute.attributesAccept(clazz, method, this); 290 } 291 292 293 public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) 294 { 295 // Fix the types of the local variables. 296 localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); 297 } 298 299 300 public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) 301 { 302 // Fix the signatures of the local variables. 303 localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); 304 } 305 306 307 public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) 308 { 309 // Has the signature changed? 310 String signature = signatureAttribute.getSignature(clazz); 311 String newSignature = newDescriptor(signature, 312 signatureAttribute.referencedClasses); 313 314 if (!signature.equals(newSignature)) 315 { 316 // Update the signature. 317 signatureAttribute.u2signatureIndex = 318 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); 319 } 320 } 321 322 323 public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) 324 { 325 // Fix the annotations. 326 annotationsAttribute.annotationsAccept(clazz, this); 327 } 328 329 330 public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) 331 { 332 // Fix the annotations. 333 parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); 334 } 335 336 337 public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) 338 { 339 // Fix the annotation. 340 annotationDefaultAttribute.defaultValueAccept(clazz, this); 341 } 342 343 344 // Implementations for InnerClassesInfoVisitor. 345 346 public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) 347 { 348 // Fix the inner class name. 349 int innerClassIndex = innerClassesInfo.u2innerClassIndex; 350 int innerNameIndex = innerClassesInfo.u2innerNameIndex; 351 if (innerClassIndex != 0 && 352 innerNameIndex != 0) 353 { 354 String newInnerName = clazz.getClassName(innerClassIndex); 355 int index = newInnerName.lastIndexOf(ClassConstants.INNER_CLASS_SEPARATOR); 356 if (index >= 0) 357 { 358 innerClassesInfo.u2innerNameIndex = 359 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newInnerName.substring(index + 1)); 360 } 361 } 362 } 363 364 365 // Implementations for LocalVariableInfoVisitor. 366 367 public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) 368 { 369 // Has the descriptor changed? 370 String descriptor = localVariableInfo.getDescriptor(clazz); 371 String newDescriptor = newDescriptor(descriptor, 372 localVariableInfo.referencedClass); 373 374 if (!descriptor.equals(newDescriptor)) 375 { 376 // Refer to a new Utf8 entry. 377 localVariableInfo.u2descriptorIndex = 378 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor); 379 } 380 } 381 382 // Implementations for LocalVariableTypeInfoVisitor. 383 384 public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) 385 { 386 // Has the signature changed? 387 String signature = localVariableTypeInfo.getSignature(clazz); 388 String newSignature = newDescriptor(signature, 389 localVariableTypeInfo.referencedClasses); 390 391 if (!signature.equals(newSignature)) 392 { 393 // Update the signature. 394 localVariableTypeInfo.u2signatureIndex = 395 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); 396 } 397 } 398 399 // Implementations for AnnotationVisitor. 400 401 public void visitAnnotation(Clazz clazz, Annotation annotation) 402 { 403 // Has the type changed? 404 String typeName = annotation.getType(clazz); 405 String newTypeName = newDescriptor(typeName, 406 annotation.referencedClasses); 407 408 if (!typeName.equals(newTypeName)) 409 { 410 // Update the type. 411 annotation.u2typeIndex = 412 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName); 413 } 414 415 // Fix the element values. 416 annotation.elementValuesAccept(clazz, this); 417 } 418 419 420 // Implementations for ElementValueVisitor. 421 422 public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) 423 { 424 } 425 426 427 public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) 428 { 429 // Has the type name chamged? 430 String typeName = enumConstantElementValue.getTypeName(clazz); 431 String newTypeName = newDescriptor(typeName, 432 enumConstantElementValue.referencedClasses); 433 434 if (!typeName.equals(newTypeName)) 435 { 436 // Update the type name. 437 enumConstantElementValue.u2typeNameIndex = 438 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName); 439 } 440 } 441 442 443 public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) 444 { 445 // Has the class info changed? 446 String className = classElementValue.getClassName(clazz); 447 String newClassName = newDescriptor(className, 448 classElementValue.referencedClasses); 449 450 if (!className.equals(newClassName)) 451 { 452 // Update the class info. 453 classElementValue.u2classInfoIndex = 454 new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName); 455 } 456 } 457 458 459 public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) 460 { 461 // Fix the annotation. 462 annotationElementValue.annotationAccept(clazz, this); 463 } 464 465 466 public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) 467 { 468 // Fix the element values. 469 arrayElementValue.elementValuesAccept(clazz, annotation, this); 470 } 471 472 473 // Small utility methods. 474 475 private static String newDescriptor(String descriptor, 476 Clazz referencedClass) 477 { 478 // If there is no referenced class, the descriptor won't change. 479 if (referencedClass == null) 480 { 481 return descriptor; 482 } 483 484 // Unravel and reconstruct the class element of the descriptor. 485 DescriptorClassEnumeration descriptorClassEnumeration = 486 new DescriptorClassEnumeration(descriptor); 487 488 StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length()); 489 newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff()); 490 491 // Only if the descriptor contains a class name (e.g. with an array of 492 // primitive types), the descriptor can change. 493 if (descriptorClassEnumeration.hasMoreClassNames()) 494 { 495 String className = descriptorClassEnumeration.nextClassName(); 496 String fluff = descriptorClassEnumeration.nextFluff(); 497 498 String newClassName = newClassName(className, 499 referencedClass); 500 501 newDescriptorBuffer.append(newClassName); 502 newDescriptorBuffer.append(fluff); 503 } 504 505 return newDescriptorBuffer.toString(); 506 } 507 508 509 private static String newDescriptor(String descriptor, 510 Clazz[] referencedClasses) 511 { 512 // If there are no referenced classes, the descriptor won't change. 513 if (referencedClasses == null || 514 referencedClasses.length == 0) 515 { 516 return descriptor; 517 } 518 519 // Unravel and reconstruct the class elements of the descriptor. 520 DescriptorClassEnumeration descriptorClassEnumeration = 521 new DescriptorClassEnumeration(descriptor); 522 523 StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length()); 524 newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff()); 525 526 int index = 0; 527 while (descriptorClassEnumeration.hasMoreClassNames()) 528 { 529 String className = descriptorClassEnumeration.nextClassName(); 530 boolean isInnerClassName = descriptorClassEnumeration.isInnerClassName(); 531 String fluff = descriptorClassEnumeration.nextFluff(); 532 533 String newClassName = newClassName(className, 534 referencedClasses[index++]); 535 536 // Strip the outer class name again, if it's an inner class. 537 if (isInnerClassName) 538 { 539 newClassName = 540 newClassName.substring(newClassName.lastIndexOf(ClassConstants.INNER_CLASS_SEPARATOR)+1); 541 } 542 543 newDescriptorBuffer.append(newClassName); 544 newDescriptorBuffer.append(fluff); 545 } 546 547 return newDescriptorBuffer.toString(); 548 } 549 550 551 /** 552 * Returns a unique class member name, based on the given name and descriptor. 553 */ 554 private String newUniqueMemberName(String name, String descriptor) 555 { 556 return name.equals(ClassConstants.METHOD_NAME_INIT) ? 557 ClassConstants.METHOD_NAME_INIT : 558 name + ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); 559 } 560 561 562 /** 563 * Returns the new class name based on the given class name and the new 564 * name of the given referenced class. Class names of array types 565 * are handled properly. 566 */ 567 private static String newClassName(String className, 568 Clazz referencedClass) 569 { 570 // If there is no referenced class, the class name won't change. 571 if (referencedClass == null) 572 { 573 return className; 574 } 575 576 // Reconstruct the class name. 577 String newClassName = referencedClass.getName(); 578 579 // Is it an array type? 580 if (className.charAt(0) == ClassConstants.TYPE_ARRAY) 581 { 582 // Add the array prefixes and suffix "[L...;". 583 newClassName = 584 className.substring(0, className.indexOf(ClassConstants.TYPE_CLASS_START)+1) + 585 newClassName + 586 ClassConstants.TYPE_CLASS_END; 587 } 588 589 return newClassName; 590 } 591} 592