001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.internal.plastic; 014 015import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor; 016import org.apache.tapestry5.internal.plastic.asm.Opcodes; 017import org.apache.tapestry5.internal.plastic.asm.Type; 018import org.apache.tapestry5.internal.plastic.asm.tree.*; 019import org.apache.tapestry5.plastic.*; 020 021import java.io.IOException; 022import java.lang.annotation.Annotation; 023import java.lang.reflect.Constructor; 024import java.lang.reflect.Method; 025import java.lang.reflect.Modifier; 026import java.util.*; 027 028@SuppressWarnings("all") 029public class PlasticClassImpl extends Lockable implements PlasticClass, InternalPlasticClassTransformation, Opcodes 030{ 031 private static final String NOTHING_TO_VOID = "()V"; 032 033 static final String CONSTRUCTOR_NAME = "<init>"; 034 035 private static final String OBJECT_INT_TO_OBJECT = "(Ljava/lang/Object;I)Ljava/lang/Object;"; 036 037 private static final String OBJECT_INT_OBJECT_TO_VOID = "(Ljava/lang/Object;ILjava/lang/Object;)V"; 038 039 private static final String OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT = String.format( 040 "(Ljava/lang/Object;I[Ljava/lang/Object;)%s", toDesc(Type.getInternalName(MethodInvocationResult.class))); 041 042 static final String ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME = PlasticInternalUtils 043 .toInternalName(AbstractMethodInvocation.class.getName()); 044 045 private static final String HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME = Type 046 .getInternalName(PlasticClassHandleShim.class); 047 048 static final String STATIC_CONTEXT_INTERNAL_NAME = Type.getInternalName(StaticContext.class); 049 050 private static final String INSTANCE_CONTEXT_INTERNAL_NAME = Type.getInternalName(InstanceContext.class); 051 052 private static final String INSTANCE_CONTEXT_DESC = toDesc(INSTANCE_CONTEXT_INTERNAL_NAME); 053 054 private static final String CONSTRUCTOR_DESC = String.format("(L%s;L%s;)V", STATIC_CONTEXT_INTERNAL_NAME, 055 INSTANCE_CONTEXT_INTERNAL_NAME); 056 057 static final Method STATIC_CONTEXT_GET_METHOD = toMethod(StaticContext.class, "get", int.class); 058 059 static final Method COMPUTED_VALUE_GET_METHOD = toMethod(ComputedValue.class, "get", InstanceContext.class); 060 061 private static final Method CONSTRUCTOR_CALLBACK_METHOD = toMethod(ConstructorCallback.class, "onConstruct", 062 Object.class, InstanceContext.class); 063 064 private static String toDesc(String internalName) 065 { 066 return "L" + internalName + ";"; 067 } 068 069 private static Method toMethod(Class declaringClass, String methodName, Class... parameterTypes) 070 { 071 return PlasticUtils.getMethod(declaringClass, methodName, parameterTypes); 072 } 073 074 static <T> T safeArrayDeref(T[] array, int index) 075 { 076 if (array == null) 077 return null; 078 079 return array[index]; 080 } 081 082 // Now past the inner classes; these are the instance variables of PlasticClassImpl proper: 083 084 final ClassNode classNode; 085 086 final PlasticClassPool pool; 087 088 private final boolean proxy; 089 090 final String className; 091 092 private final String superClassName; 093 094 private final AnnotationAccess annotationAccess; 095 096 // All the non-introduced (and non-constructor) methods, in sorted order 097 098 private final List<PlasticMethodImpl> methods; 099 100 private final Map<MethodDescription, PlasticMethod> description2method = new HashMap<MethodDescription, PlasticMethod>(); 101 102 final Set<String> methodNames = new HashSet<String>(); 103 104 private final List<ConstructorCallback> constructorCallbacks = PlasticInternalUtils.newList(); 105 106 // All non-introduced instance fields 107 108 private final List<PlasticFieldImpl> fields; 109 110 /** 111 * Methods that require special attention inside {@link #createInstantiator()} because they 112 * have method advice. 113 */ 114 final Set<PlasticMethodImpl> advisedMethods = PlasticInternalUtils.newSet(); 115 116 final NameCache nameCache = new NameCache(); 117 118 // This is generated from fields, as necessary 119 List<PlasticField> unclaimedFields; 120 121 private final Set<String> fieldNames = PlasticInternalUtils.newSet(); 122 123 final StaticContext staticContext; 124 125 final InheritanceData parentInheritanceData, inheritanceData; 126 127 // MethodNodes in which field transformations should occur; this is most existing and 128 // introduced methods, outside of special access methods. 129 130 final Set<MethodNode> fieldTransformMethods = PlasticInternalUtils.newSet(); 131 132 // Tracks any methods that the Shim class uses to gain access to fields; used to ensure that 133 // such methods are not optimized away incorrectly. 134 final Set<MethodNode> shimInvokedMethods = PlasticInternalUtils.newSet(); 135 136 137 /** 138 * Tracks instrumentations of fields of this class, including private fields which are not published into the 139 * {@link PlasticClassPool}. 140 */ 141 private final FieldInstrumentations fieldInstrumentations; 142 143 /** 144 * This normal no-arguments constructor, or null. By the end of the transformation 145 * this will be converted into an ordinary method. 146 */ 147 private MethodNode originalConstructor; 148 149 private final MethodNode newConstructor; 150 151 final InstructionBuilder constructorBuilder; 152 153 private String instanceContextFieldName; 154 155 private Class<?> transformedClass; 156 157 // Indexes used to identify fields or methods in the shim 158 int nextFieldIndex = 0; 159 160 int nextMethodIndex = 0; 161 162 // Set of fields that need to contribute to the shim and gain access to it 163 164 final Set<PlasticFieldImpl> shimFields = PlasticInternalUtils.newSet(); 165 166 // Set of methods that need to contribute to the shim and gain access to it 167 168 final Set<PlasticMethodImpl> shimMethods = PlasticInternalUtils.newSet(); 169 170 final ClassNode implementationClassNode; 171 172 private ClassNode interfaceClassNode; 173 174 /** 175 * @param classNode 176 * @param implementationClassNode 177 * @param pool 178 * @param parentInheritanceData 179 * @param parentStaticContext 180 * @param proxy 181 */ 182 public PlasticClassImpl(ClassNode classNode, ClassNode implementationClassNode, PlasticClassPool pool, InheritanceData parentInheritanceData, 183 StaticContext parentStaticContext, boolean proxy) 184 { 185 this.classNode = classNode; 186 this.pool = pool; 187 this.proxy = proxy; 188 this.implementationClassNode = implementationClassNode; 189 190 staticContext = parentStaticContext.dupe(); 191 192 className = PlasticInternalUtils.toClassName(classNode.name); 193 superClassName = PlasticInternalUtils.toClassName(classNode.superName); 194 195 fieldInstrumentations = new FieldInstrumentations(classNode.superName); 196 197 annotationAccess = new DelegatingAnnotationAccess(pool.createAnnotationAccess(classNode.visibleAnnotations), 198 pool.createAnnotationAccess(superClassName)); 199 200 this.parentInheritanceData = parentInheritanceData; 201 202 inheritanceData = parentInheritanceData.createChild(); 203 204 for (String interfaceName : classNode.interfaces) 205 { 206 inheritanceData.addInterface(interfaceName); 207 } 208 209 methods = new ArrayList(classNode.methods.size()); 210 211 String invalidConstructorMessage = invalidConstructorMessage(); 212 213 for (MethodNode node : classNode.methods) 214 { 215 if (node.name.equals(CONSTRUCTOR_NAME)) 216 { 217 if (node.desc.equals(NOTHING_TO_VOID)) 218 { 219 originalConstructor = node; 220 fieldTransformMethods.add(node); 221 } else 222 { 223 node.instructions.clear(); 224 225 newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage); 226 } 227 228 continue; 229 } 230 231 /** 232 * Static methods are not visible to the main API methods, but they must still be transformed, 233 * in case they directly access fields. In addition, track their names to avoid collisions. 234 */ 235 if (Modifier.isStatic(node.access)) 236 { 237 if (isInheritableMethod(node)) 238 { 239 inheritanceData.addMethod(node.name, node.desc); 240 } 241 242 methodNames.add(node.name); 243 244 fieldTransformMethods.add(node); 245 246 continue; 247 } 248 249 if (!Modifier.isAbstract(node.access)) 250 { 251 fieldTransformMethods.add(node); 252 } 253 254 PlasticMethodImpl pmi = new PlasticMethodImpl(this, node); 255 256 methods.add(pmi); 257 description2method.put(pmi.getDescription(), pmi); 258 259 if (isInheritableMethod(node)) 260 { 261 inheritanceData.addMethod(node.name, node.desc); 262 } 263 264 methodNames.add(node.name); 265 } 266 267 methodNames.addAll(parentInheritanceData.methodNames()); 268 269 Collections.sort(methods); 270 271 fields = new ArrayList(classNode.fields.size()); 272 273 for (FieldNode node : classNode.fields) 274 { 275 fieldNames.add(node.name); 276 277 // Ignore static fields. 278 279 if (Modifier.isStatic(node.access)) 280 continue; 281 282 // When we instrument the field such that it must be private, we'll get an exception. 283 284 fields.add(new PlasticFieldImpl(this, node)); 285 } 286 287 Collections.sort(fields); 288 289 // TODO: Make the output class's constructor protected, and create a shim class to instantiate it 290 // efficiently (without reflection). 291 newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null); 292 constructorBuilder = newBuilder(newConstructor); 293 294 // Start by calling the super-class no args constructor 295 296 if (parentInheritanceData.isTransformed()) 297 { 298 // If the parent is transformed, our first step is always to invoke its constructor. 299 300 constructorBuilder.loadThis().loadArgument(0).loadArgument(1); 301 constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(), 302 InstanceContext.class.getName()); 303 } else 304 { 305 // Assumes the base class includes a visible constructor that takes no arguments. 306 // TODO: Do a proper check for this case and throw a meaningful exception 307 // if not present. 308 309 constructorBuilder.loadThis().invokeConstructor(superClassName); 310 } 311 312 // During the transformation, we'll be adding code to the constructor to pull values 313 // out of the static or instance context and assign them to fields. 314 315 // Later on, we'll add the RETURN opcode 316 } 317 318 private String invalidConstructorMessage() 319 { 320 return String.format("Class %s has been transformed and may not be directly instantiated.", className); 321 } 322 323 @Override 324 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) 325 { 326 check(); 327 328 return annotationAccess.hasAnnotation(annotationType); 329 } 330 331 @Override 332 public <T extends Annotation> T getAnnotation(Class<T> annotationType) 333 { 334 check(); 335 336 return annotationAccess.getAnnotation(annotationType); 337 } 338 339 private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, ClassNode source) 340 { 341 if (source != null) 342 { 343 344 for (MethodNode implementationNode : source.methods) 345 { 346 // Find corresponding method in the implementation class MethodNode 347 if (methodNode.name.equals(implementationNode.name) && methodNode.desc.equals(implementationNode.desc)) 348 { 349 350 // Copied and adapted from MethodNode.accept(). We want annotation info, but not code nor attributes 351 // Otherwise, we get ClassFormatError (Illegal exception table range) later 352 353 // visits the method attributes 354 int i, j, n; 355 if (implementationNode.annotationDefault != null) 356 { 357 AnnotationVisitor av = methodNode.visitAnnotationDefault(); 358 AnnotationNode.accept(av, null, implementationNode.annotationDefault); 359 if (av != null) 360 { 361 av.visitEnd(); 362 } 363 } 364 n = implementationNode.visibleAnnotations == null ? 0 : implementationNode.visibleAnnotations.size(); 365 for (i = 0; i < n; ++i) 366 { 367 AnnotationNode an = implementationNode.visibleAnnotations.get(i); 368 an.accept(methodNode.visitAnnotation(an.desc, true)); 369 } 370 n = implementationNode.invisibleAnnotations == null ? 0 : implementationNode.invisibleAnnotations.size(); 371 for (i = 0; i < n; ++i) 372 { 373 AnnotationNode an = implementationNode.invisibleAnnotations.get(i); 374 an.accept(methodNode.visitAnnotation(an.desc, false)); 375 } 376 n = implementationNode.visibleParameterAnnotations == null 377 ? 0 378 : implementationNode.visibleParameterAnnotations.length; 379 for (i = 0; i < n; ++i) 380 { 381 List<?> l = implementationNode.visibleParameterAnnotations[i]; 382 if (l == null) 383 { 384 continue; 385 } 386 for (j = 0; j < l.size(); ++j) 387 { 388 AnnotationNode an = (AnnotationNode) l.get(j); 389 an.accept(methodNode.visitParameterAnnotation(i, an.desc, true)); 390 } 391 } 392 n = implementationNode.invisibleParameterAnnotations == null 393 ? 0 394 : implementationNode.invisibleParameterAnnotations.length; 395 for (i = 0; i < n; ++i) 396 { 397 List<?> l = implementationNode.invisibleParameterAnnotations[i]; 398 if (l == null) 399 { 400 continue; 401 } 402 for (j = 0; j < l.size(); ++j) 403 { 404 AnnotationNode an = (AnnotationNode) l.get(j); 405 an.accept(methodNode.visitParameterAnnotation(i, an.desc, false)); 406 } 407 } 408 409 methodNode.visitEnd(); 410 411 break; 412 413 } 414 415 } 416 417 } 418 419 } 420 421 @Override 422 public PlasticClass proxyInterface(Class interfaceType, PlasticField field) 423 { 424 check(); 425 426 assert field != null; 427 428 introduceInterface(interfaceType); 429 430 for (Method m : interfaceType.getMethods()) 431 { 432 introduceMethod(m).delegateTo(field); 433 } 434 435 return this; 436 } 437 438 @Override 439 public ClassInstantiator createInstantiator() 440 { 441 lock(); 442 443 addClassAnnotations(implementationClassNode); 444 445 createShimIfNeeded(); 446 447 interceptFieldAccess(); 448 449 rewriteAdvisedMethods(); 450 451 completeConstructor(); 452 453 transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext); 454 455 return createInstantiatorFromClass(transformedClass); 456 } 457 458 private void addClassAnnotations(ClassNode otherClassNode) 459 { 460 // Copy annotations from implementation if available. 461 // Code adapted from ClassNode.accept(), as we just want to copy 462 // the annotations and nothing more. 463 if (otherClassNode != null) 464 { 465 466 int i, n; 467 n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size(); 468 for (i = 0; i < n; ++i) 469 { 470 AnnotationNode an = otherClassNode.visibleAnnotations.get(i); 471 an.accept(classNode.visitAnnotation(an.desc, true)); 472 } 473 n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size(); 474 for (i = 0; i < n; ++i) 475 { 476 AnnotationNode an = otherClassNode.invisibleAnnotations.get(i); 477 an.accept(classNode.visitAnnotation(an.desc, false)); 478 } 479 480 } 481 } 482 483 private ClassInstantiator createInstantiatorFromClass(Class clazz) 484 { 485 try 486 { 487 Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class); 488 489 return new ClassInstantiatorImpl(clazz, ctor, staticContext); 490 } catch (Exception ex) 491 { 492 throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s", 493 clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex); 494 } 495 } 496 497 private void completeConstructor() 498 { 499 if (originalConstructor != null) 500 { 501 convertOriginalConstructorToMethod(); 502 } 503 504 invokeCallbacks(); 505 506 constructorBuilder.returnResult(); 507 508 classNode.methods.add(newConstructor); 509 } 510 511 private void invokeCallbacks() 512 { 513 for (ConstructorCallback callback : constructorCallbacks) 514 { 515 invokeCallback(callback); 516 } 517 } 518 519 private void invokeCallback(ConstructorCallback callback) 520 { 521 int index = staticContext.store(callback); 522 523 // First, load the callback 524 525 constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName()); 526 527 // Load this and the InstanceContext 528 constructorBuilder.loadThis().loadArgument(1); 529 530 constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD); 531 } 532 533 534 /** 535 * Convert the original constructor into a private method invoked from the 536 * generated constructor. 537 */ 538 private void convertOriginalConstructorToMethod() 539 { 540 String initializerName = makeUnique(methodNames, "initializeInstance"); 541 542 int originalAccess = originalConstructor.access; 543 544 originalConstructor.access = ACC_PRIVATE; 545 originalConstructor.name = initializerName; 546 547 stripOutSuperConstructorCall(originalConstructor); 548 549 constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName); 550 551 // And replace it with a constructor that throws an exception 552 553 MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, 554 null); 555 556 newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage()); 557 558 classNode.methods.add(replacementConstructor); 559 } 560 561 private void stripOutSuperConstructorCall(MethodNode cons) 562 { 563 InsnList ins = cons.instructions; 564 565 ListIterator li = ins.iterator(); 566 567 // Look for the ALOAD 0 (i.e., push this on the stack) 568 while (li.hasNext()) 569 { 570 AbstractInsnNode node = (AbstractInsnNode) li.next(); 571 572 if (node.getOpcode() == ALOAD) 573 { 574 VarInsnNode varNode = (VarInsnNode) node; 575 576 assert varNode.var == 0; 577 578 // Remove the ALOAD 579 li.remove(); 580 break; 581 } 582 } 583 584 // Look for the call to the super-class, an INVOKESPECIAL 585 while (li.hasNext()) 586 { 587 AbstractInsnNode node = (AbstractInsnNode) li.next(); 588 589 if (node.getOpcode() == INVOKESPECIAL) 590 { 591 MethodInsnNode mnode = (MethodInsnNode) node; 592 593 assert mnode.owner.equals(classNode.superName); 594 assert mnode.name.equals(CONSTRUCTOR_NAME); 595 assert mnode.desc.equals(cons.desc); 596 597 li.remove(); 598 return; 599 } 600 } 601 602 throw new AssertionError("Could not convert constructor to simple method."); 603 } 604 605 @Override 606 public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType) 607 { 608 check(); 609 610 List<PlasticField> result = getAllFields(); 611 612 Iterator<PlasticField> iterator = result.iterator(); 613 614 while (iterator.hasNext()) 615 { 616 PlasticField plasticField = iterator.next(); 617 618 if (!plasticField.hasAnnotation(annotationType)) 619 iterator.remove(); 620 } 621 622 return result; 623 } 624 625 @Override 626 public List<PlasticField> getAllFields() 627 { 628 check(); 629 630 return new ArrayList<PlasticField>(fields); 631 } 632 633 @Override 634 public List<PlasticField> getUnclaimedFields() 635 { 636 check(); 637 638 // Initially null, and set back to null by PlasticField.claim(). 639 640 if (unclaimedFields == null) 641 { 642 unclaimedFields = new ArrayList<PlasticField>(fields.size()); 643 644 for (PlasticField f : fields) 645 { 646 if (!f.isClaimed()) 647 unclaimedFields.add(f); 648 } 649 } 650 651 return unclaimedFields; 652 } 653 654 @Override 655 public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes, 656 String[] exceptionTypes) 657 { 658 check(); 659 660 assert PlasticInternalUtils.isNonBlank(typeName); 661 assert PlasticInternalUtils.isNonBlank(suggestedName); 662 663 String name = makeUnique(methodNames, suggestedName); 664 665 MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null, 666 exceptionTypes); 667 668 return introduceMethod(description); 669 } 670 671 @Override 672 public PlasticField introduceField(String className, String suggestedName) 673 { 674 check(); 675 676 assert PlasticInternalUtils.isNonBlank(className); 677 assert PlasticInternalUtils.isNonBlank(suggestedName); 678 679 String name = makeUnique(fieldNames, suggestedName); 680 681 // No signature and no initial value 682 683 FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null); 684 685 classNode.fields.add(fieldNode); 686 687 fieldNames.add(name); 688 689 PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode); 690 691 return newField; 692 } 693 694 @Override 695 public PlasticField introduceField(Class fieldType, String suggestedName) 696 { 697 assert fieldType != null; 698 699 return introduceField(nameCache.toTypeName(fieldType), suggestedName); 700 } 701 702 String makeUnique(Set<String> values, String input) 703 { 704 return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input; 705 } 706 707 @Override 708 public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType) 709 { 710 check(); 711 712 List<PlasticMethod> result = getMethods(); 713 Iterator<PlasticMethod> iterator = result.iterator(); 714 715 while (iterator.hasNext()) 716 { 717 PlasticMethod method = iterator.next(); 718 719 if (!method.hasAnnotation(annotationType)) 720 iterator.remove(); 721 } 722 723 return result; 724 } 725 726 @Override 727 public List<PlasticMethod> getMethods() 728 { 729 check(); 730 731 return new ArrayList<PlasticMethod>(methods); 732 } 733 734 @Override 735 public PlasticMethod introduceMethod(MethodDescription description) 736 { 737 check(); 738 739 if (Modifier.isAbstract(description.modifiers)) 740 { 741 description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT); 742 } 743 744 PlasticMethod result = description2method.get(description); 745 746 if (result == null) 747 { 748 result = createNewMethod(description); 749 750 description2method.put(description, result); 751 } 752 753 methodNames.add(description.methodName); 754 755 // Note that is it not necessary to add the new MethodNode to 756 // fieldTransformMethods (the default implementations provided by introduceMethod() do not 757 // ever access instance fields) ... unless the caller invokes changeImplementation(). 758 759 return result; 760 } 761 762 @Override 763 public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback) 764 { 765 check(); 766 767 // TODO: optimize this so that a default implementation is not created. 768 769 return introduceMethod(description).changeImplementation(callback); 770 } 771 772 @Override 773 public PlasticMethod introduceMethod(Method method) 774 { 775 check(); 776 777 return introduceMethod(new MethodDescription(method)); 778 } 779 780 void addMethod(MethodNode methodNode) 781 { 782 classNode.methods.add(methodNode); 783 784 methodNames.add(methodNode.name); 785 786 if (isInheritableMethod(methodNode)) 787 { 788 inheritanceData.addMethod(methodNode.name, methodNode.desc); 789 } 790 } 791 792 private PlasticMethod createNewMethod(MethodDescription description) 793 { 794 if (Modifier.isStatic(description.modifiers)) 795 throw new IllegalArgumentException(String.format( 796 "Unable to introduce method '%s' into class %s: introduced methods may not be static.", 797 description, className)); 798 799 String desc = nameCache.toDesc(description); 800 801 String[] exceptions = new String[description.checkedExceptionTypes.length]; 802 for (int i = 0; i < exceptions.length; i++) 803 { 804 exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]); 805 } 806 807 MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc, 808 description.genericSignature, exceptions); 809 boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc); 810 811 if (!isOverride) 812 { 813 addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode); 814 addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode); 815 } 816 817 if (isOverride) 818 createOverrideOfBaseClassImpl(description, methodNode); 819 else 820 createNewMethodImpl(description, methodNode); 821 822 addMethod(methodNode); 823 824 return new PlasticMethodImpl(this, methodNode); 825 } 826 827 private boolean isDefaultMethod(Method method) 828 { 829 return method.getDeclaringClass().isInterface() && 830 (method.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_ABSTRACT)) == Opcodes.ACC_PUBLIC; 831 } 832 833 private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode) 834 { 835 newBuilder(methodDescription, methodNode).returnDefaultValue(); 836 } 837 838 private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode) 839 { 840 InstructionBuilder builder = newBuilder(methodDescription, methodNode); 841 842 builder.loadThis(); 843 builder.loadArguments(); 844 builder.invokeSpecial(superClassName, methodDescription); 845 builder.returnResult(); 846 } 847 848 /** 849 * Iterates over all non-introduced methods, including the original constructor. For each 850 * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field, 851 * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim} 852 * for the class has been created, as the shim may create methods that contain references to fields that may be 853 * subject to field access interception. 854 */ 855 private void interceptFieldAccess() 856 { 857 for (MethodNode node : fieldTransformMethods) 858 { 859 // Intercept field access inside the method, tracking which access methods 860 // are actually used by removing them from accessMethods 861 862 interceptFieldAccess(node); 863 } 864 } 865 866 /** 867 * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so 868 * a shim class must be created to facilitate read/write access to fields, or invocation of methods. 869 */ 870 private void createShimIfNeeded() 871 { 872 if (shimFields.isEmpty() && shimMethods.isEmpty()) 873 return; 874 875 PlasticClassHandleShim shim = createShimInstance(); 876 877 installShim(shim); 878 } 879 880 public void installShim(PlasticClassHandleShim shim) 881 { 882 for (PlasticFieldImpl f : shimFields) 883 { 884 f.installShim(shim); 885 } 886 887 for (PlasticMethodImpl m : shimMethods) 888 { 889 m.installShim(shim); 890 } 891 } 892 893 public PlasticClassHandleShim createShimInstance() 894 { 895 String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID()); 896 897 ClassNode shimClassNode = new ClassNode(); 898 899 shimClassNode.visit(V1_5, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME, 900 null); 901 902 implementConstructor(shimClassNode); 903 904 if (!shimFields.isEmpty()) 905 { 906 implementShimGet(shimClassNode); 907 implementShimSet(shimClassNode); 908 } 909 910 if (!shimMethods.isEmpty()) 911 { 912 implementShimInvoke(shimClassNode); 913 } 914 915 return instantiateShim(shimClassNode); 916 } 917 918 private void implementConstructor(ClassNode shimClassNode) 919 { 920 MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null); 921 922 InstructionBuilder builder = newBuilder(mn); 923 924 builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult(); 925 926 shimClassNode.methods.add(mn); 927 928 } 929 930 private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode) 931 { 932 try 933 { 934 Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode); 935 936 return (PlasticClassHandleShim) shimClass.newInstance(); 937 } catch (Exception ex) 938 { 939 throw new RuntimeException( 940 String.format("Unable to instantiate shim class %s for plastic class %s: %s", 941 PlasticInternalUtils.toClassName(shimClassNode.name), className, 942 PlasticInternalUtils.toMessage(ex)), ex); 943 } 944 } 945 946 private void implementShimGet(ClassNode shimClassNode) 947 { 948 MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null); 949 950 InstructionBuilder builder = newBuilder(mn); 951 952 // Arg 0 is the target instance 953 // Arg 1 is the index 954 955 builder.loadArgument(0).checkcast(className); 956 builder.loadArgument(1); 957 958 builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() 959 { 960 @Override 961 public void doSwitch(SwitchBlock block) 962 { 963 for (PlasticFieldImpl f : shimFields) 964 { 965 f.extendShimGet(block); 966 } 967 } 968 }); 969 970 shimClassNode.methods.add(mn); 971 } 972 973 private void implementShimSet(ClassNode shimClassNode) 974 { 975 MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null); 976 977 InstructionBuilder builder = newBuilder(mn); 978 979 // Arg 0 is the target instance 980 // Arg 1 is the index 981 // Arg 2 is the new value 982 983 builder.loadArgument(0).checkcast(className); 984 builder.loadArgument(2); 985 986 builder.loadArgument(1); 987 988 builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() 989 { 990 @Override 991 public void doSwitch(SwitchBlock block) 992 { 993 for (PlasticFieldImpl f : shimFields) 994 { 995 f.extendShimSet(block); 996 } 997 } 998 }); 999 1000 builder.returnResult(); 1001 1002 shimClassNode.methods.add(mn); 1003 } 1004 1005 private void implementShimInvoke(ClassNode shimClassNode) 1006 { 1007 MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null, 1008 null); 1009 1010 InstructionBuilder builder = newBuilder(mn); 1011 1012 // Arg 0 is the target instance 1013 // Arg 1 is the index 1014 // Arg 2 is the object array of parameters 1015 1016 builder.loadArgument(0).checkcast(className); 1017 1018 builder.loadArgument(1); 1019 1020 builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback() 1021 { 1022 @Override 1023 public void doSwitch(SwitchBlock block) 1024 { 1025 for (PlasticMethodImpl m : shimMethods) 1026 { 1027 m.extendShimInvoke(block); 1028 } 1029 } 1030 }); 1031 1032 shimClassNode.methods.add(mn); 1033 } 1034 1035 private void rewriteAdvisedMethods() 1036 { 1037 for (PlasticMethodImpl method : advisedMethods) 1038 { 1039 method.rewriteMethodForAdvice(); 1040 } 1041 } 1042 1043 private void interceptFieldAccess(MethodNode methodNode) 1044 { 1045 InsnList insns = methodNode.instructions; 1046 1047 ListIterator it = insns.iterator(); 1048 1049 while (it.hasNext()) 1050 { 1051 AbstractInsnNode node = (AbstractInsnNode) it.next(); 1052 1053 int opcode = node.getOpcode(); 1054 1055 if (opcode != GETFIELD && opcode != PUTFIELD) 1056 { 1057 continue; 1058 } 1059 1060 FieldInsnNode fnode = (FieldInsnNode) node; 1061 1062 FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD); 1063 1064 if (instrumentation == null) 1065 { 1066 continue; 1067 } 1068 1069 // Replace the field access node with the appropriate method invocation. 1070 1071 insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription)); 1072 1073 it.remove(); 1074 } 1075 } 1076 1077 private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead) 1078 { 1079 // First look in the local fieldInstrumentations, which contains private field instrumentations 1080 // (as well as non-private ones). 1081 1082 String searchStart = node.owner; 1083 1084 if (searchStart.equals(classNode.name)) 1085 { 1086 FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead); 1087 1088 if (result != null) 1089 { 1090 return result; 1091 } 1092 1093 // Slight optimization: start the search in the super-classes' fields, since we've already 1094 // checked this classes fields. 1095 1096 searchStart = classNode.superName; 1097 } 1098 1099 return pool.getFieldInstrumentation(searchStart, node.name, forRead); 1100 } 1101 1102 String getInstanceContextFieldName() 1103 { 1104 if (instanceContextFieldName == null) 1105 { 1106 instanceContextFieldName = makeUnique(fieldNames, "instanceContext"); 1107 1108 // TODO: We could use a protected field and only initialize 1109 // it once, in the first base class where it is needed, though that raises the possibilities 1110 // of name conflicts (a subclass might introduce a field with a conflicting name). 1111 1112 FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC, 1113 null, null); 1114 1115 classNode.fields.add(node); 1116 1117 // Extend the constructor to store the context in a field. 1118 1119 constructorBuilder.loadThis().loadArgument(1) 1120 .putField(className, instanceContextFieldName, InstanceContext.class); 1121 } 1122 1123 return instanceContextFieldName; 1124 } 1125 1126 /** 1127 * Creates a new private final field and initializes its value (using the StaticContext). 1128 */ 1129 String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType, 1130 Object injectedFieldValue) 1131 { 1132 String name = makeUnique(fieldNames, suggestedFieldName); 1133 1134 FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null); 1135 1136 classNode.fields.add(field); 1137 1138 initializeFieldFromStaticContext(name, fieldType, injectedFieldValue); 1139 1140 return name; 1141 } 1142 1143 /** 1144 * Initializes a field from the static context. The injected value is added to the static 1145 * context and the class constructor updated to assign the value from the context (which includes casting and 1146 * possibly unboxing). 1147 */ 1148 void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue) 1149 { 1150 int index = staticContext.store(injectedFieldValue); 1151 1152 // Although it feels nicer to do the loadThis() later and then swap(), that breaks 1153 // on primitive longs and doubles, so its just easier to do the loadThis() first 1154 // so its at the right place on the stack for the putField(). 1155 1156 constructorBuilder.loadThis(); 1157 1158 constructorBuilder.loadArgument(0).loadConstant(index); 1159 constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD); 1160 constructorBuilder.castOrUnbox(fieldType); 1161 1162 constructorBuilder.putField(className, fieldName, fieldType); 1163 } 1164 1165 void pushInstanceContextFieldOntoStack(InstructionBuilder builder) 1166 { 1167 builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class); 1168 } 1169 1170 @Override 1171 public PlasticClass getPlasticClass() 1172 { 1173 return this; 1174 } 1175 1176 @Override 1177 public Class<?> getTransformedClass() 1178 { 1179 if (transformedClass == null) 1180 throw new IllegalStateException(String.format( 1181 "Transformed class %s is not yet available because the transformation is not yet complete.", 1182 className)); 1183 1184 return transformedClass; 1185 } 1186 1187 private boolean isInheritableMethod(MethodNode node) 1188 { 1189 return !Modifier.isPrivate(node.access); 1190 } 1191 1192 @Override 1193 public String getClassName() 1194 { 1195 return className; 1196 } 1197 1198 InstructionBuilderImpl newBuilder(MethodNode mn) 1199 { 1200 return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn); 1201 } 1202 1203 InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn) 1204 { 1205 return new InstructionBuilderImpl(description, mn, nameCache); 1206 } 1207 1208 @Override 1209 public Set<PlasticMethod> introduceInterface(Class interfaceType) 1210 { 1211 check(); 1212 1213 assert interfaceType != null; 1214 1215 if (!interfaceType.isInterface()) 1216 throw new IllegalArgumentException(String.format( 1217 "Class %s is not an interface; ony interfaces may be introduced.", interfaceType.getName())); 1218 1219 String interfaceName = nameCache.toInternalName(interfaceType); 1220 1221 try 1222 { 1223 interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader()); 1224 } catch (IOException e) 1225 { 1226 throw new RuntimeException(e); 1227 } 1228 1229 if (!inheritanceData.isInterfaceImplemented(interfaceName)) 1230 { 1231 classNode.interfaces.add(interfaceName); 1232 inheritanceData.addInterface(interfaceName); 1233 } 1234 1235 addClassAnnotations(interfaceClassNode); 1236 1237 Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>(); 1238 1239 for (Method m : interfaceType.getMethods()) 1240 { 1241 MethodDescription description = new MethodDescription(m); 1242 1243 if (!isMethodImplemented(description) && !isDefaultMethod(m)) 1244 { 1245 introducedMethods.add(introduceMethod(m)); 1246 } 1247 } 1248 1249 interfaceClassNode = null; 1250 1251 return introducedMethods; 1252 } 1253 1254 @Override 1255 public PlasticClass addToString(final String toStringValue) 1256 { 1257 check(); 1258 1259 if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION)) 1260 { 1261 introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback() 1262 { 1263 @Override 1264 public void doBuild(InstructionBuilder builder) 1265 { 1266 builder.loadConstant(toStringValue).returnResult(); 1267 } 1268 }); 1269 } 1270 1271 return this; 1272 } 1273 1274 @Override 1275 public boolean isMethodImplemented(MethodDescription description) 1276 { 1277 return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description)); 1278 } 1279 1280 @Override 1281 public boolean isInterfaceImplemented(Class interfaceType) 1282 { 1283 assert interfaceType != null; 1284 assert interfaceType.isInterface(); 1285 1286 String interfaceName = nameCache.toInternalName(interfaceType); 1287 1288 return inheritanceData.isInterfaceImplemented(interfaceName); 1289 } 1290 1291 @Override 1292 public String getSuperClassName() 1293 { 1294 return superClassName; 1295 } 1296 1297 @Override 1298 public PlasticClass onConstruct(ConstructorCallback callback) 1299 { 1300 check(); 1301 1302 assert callback != null; 1303 1304 constructorCallbacks.add(callback); 1305 1306 return this; 1307 } 1308 1309 void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method) 1310 { 1311 FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); 1312 1313 fieldInstrumentations.write.put(fieldName, fi); 1314 1315 if (!(proxy || privateField)) 1316 { 1317 pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi); 1318 } 1319 } 1320 1321 void redirectFieldRead(String fieldName, boolean privateField, MethodNode method) 1322 { 1323 FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); 1324 1325 fieldInstrumentations.read.put(fieldName, fi); 1326 1327 if (!(proxy || privateField)) 1328 { 1329 pool.setFieldReadInstrumentation(classNode.name, fieldName, fi); 1330 } 1331 } 1332 1333 @Override 1334 public String toString() 1335 { 1336 return String.format("PlasticClassImpl[%s]", className); 1337 } 1338}