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