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.Array; 024import java.lang.reflect.Constructor; 025import java.lang.reflect.Method; 026import java.lang.reflect.Modifier; 027import java.util.*; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 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 205 inheritanceData = parentInheritanceData.createChild(); 206 207 for (String interfaceName : classNode.interfaces) 208 { 209 inheritanceData.addInterface(interfaceName); 210 } 211 212 methods = new ArrayList(classNode.methods.size()); 213 214 String invalidConstructorMessage = invalidConstructorMessage(); 215 216 for (MethodNode node : classNode.methods) 217 { 218 if (node.name.equals(CONSTRUCTOR_NAME)) 219 { 220 if (node.desc.equals(NOTHING_TO_VOID)) 221 { 222 originalConstructor = node; 223 fieldTransformMethods.add(node); 224 } else 225 { 226 node.instructions.clear(); 227 228 newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage); 229 } 230 231 continue; 232 } 233 234 /** 235 * Static methods are not visible to the main API methods, but they must still be transformed, 236 * in case they directly access fields. In addition, track their names to avoid collisions. 237 */ 238 if (Modifier.isStatic(node.access)) 239 { 240 if (isInheritableMethod(node)) 241 { 242 inheritanceData.addMethod(node.name, node.desc); 243 } 244 245 methodNames.add(node.name); 246 247 fieldTransformMethods.add(node); 248 249 continue; 250 } 251 252 if (!Modifier.isAbstract(node.access)) 253 { 254 fieldTransformMethods.add(node); 255 } 256 257 PlasticMethodImpl pmi = new PlasticMethodImpl(this, node); 258 259 methods.add(pmi); 260 description2method.put(pmi.getDescription(), pmi); 261 262 if (isInheritableMethod(node)) 263 { 264 inheritanceData.addMethod(node.name, node.desc); 265 } 266 267 methodNames.add(node.name); 268 } 269 270 methodNames.addAll(parentInheritanceData.methodNames()); 271 272 Collections.sort(methods); 273 274 fields = new ArrayList(classNode.fields.size()); 275 276 for (FieldNode node : classNode.fields) 277 { 278 fieldNames.add(node.name); 279 280 // Ignore static fields. 281 282 if (Modifier.isStatic(node.access)) 283 continue; 284 285 // When we instrument the field such that it must be private, we'll get an exception. 286 287 fields.add(new PlasticFieldImpl(this, node)); 288 } 289 290 Collections.sort(fields); 291 292 // TODO: Make the output class's constructor protected, and create a shim class to instantiate it 293 // efficiently (without reflection). 294 newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null); 295 constructorBuilder = newBuilder(newConstructor); 296 297 // Start by calling the super-class no args constructor 298 299 if (parentInheritanceData.isTransformed()) 300 { 301 // If the parent is transformed, our first step is always to invoke its constructor. 302 303 constructorBuilder.loadThis().loadArgument(0).loadArgument(1); 304 constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(), 305 InstanceContext.class.getName()); 306 } else 307 { 308 // Assumes the base class includes a visible constructor that takes no arguments. 309 // TODO: Do a proper check for this case and throw a meaningful exception 310 // if not present. 311 312 constructorBuilder.loadThis().invokeConstructor(superClassName); 313 } 314 315 // During the transformation, we'll be adding code to the constructor to pull values 316 // out of the static or instance context and assign them to fields. 317 318 // Later on, we'll add the RETURN opcode 319 } 320 321 private String invalidConstructorMessage() 322 { 323 return String.format("Class %s has been transformed and may not be directly instantiated.", className); 324 } 325 326 @Override 327 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) 328 { 329 check(); 330 331 return annotationAccess.hasAnnotation(annotationType); 332 } 333 334 @Override 335 public <T extends Annotation> T getAnnotation(Class<T> annotationType) 336 { 337 check(); 338 339 return annotationAccess.getAnnotation(annotationType); 340 } 341 342 private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, MethodNode implementationMethodNode) 343 { 344 // visits the method attributes 345 int i, j, n; 346 if (implementationMethodNode.annotationDefault != null) 347 { 348 AnnotationVisitor av = methodNode.visitAnnotationDefault(); 349 AnnotationNode.accept(av, null, implementationMethodNode.annotationDefault); 350 if (av != null) 351 { 352 av.visitEnd(); 353 } 354 } 355 n = implementationMethodNode.visibleAnnotations == null ? 0 : implementationMethodNode.visibleAnnotations.size(); 356 for (i = 0; i < n; ++i) 357 { 358 AnnotationNode an = implementationMethodNode.visibleAnnotations.get(i); 359 an.accept(methodNode.visitAnnotation(an.desc, true)); 360 } 361 n = implementationMethodNode.invisibleAnnotations == null ? 0 : implementationMethodNode.invisibleAnnotations.size(); 362 for (i = 0; i < n; ++i) 363 { 364 AnnotationNode an = implementationMethodNode.invisibleAnnotations.get(i); 365 an.accept(methodNode.visitAnnotation(an.desc, false)); 366 } 367 n = implementationMethodNode.visibleParameterAnnotations == null 368 ? 0 369 : implementationMethodNode.visibleParameterAnnotations.length; 370 for (i = 0; i < n; ++i) 371 { 372 List<?> l = implementationMethodNode.visibleParameterAnnotations[i]; 373 if (l == null) 374 { 375 continue; 376 } 377 for (j = 0; j < l.size(); ++j) 378 { 379 AnnotationNode an = (AnnotationNode) l.get(j); 380 an.accept(methodNode.visitParameterAnnotation(i, an.desc, true)); 381 } 382 } 383 n = implementationMethodNode.invisibleParameterAnnotations == null 384 ? 0 385 : implementationMethodNode.invisibleParameterAnnotations.length; 386 for (i = 0; i < n; ++i) 387 { 388 List<?> l = implementationMethodNode.invisibleParameterAnnotations[i]; 389 if (l == null) 390 { 391 continue; 392 } 393 for (j = 0; j < l.size(); ++j) 394 { 395 AnnotationNode an = (AnnotationNode) l.get(j); 396 an.accept(methodNode.visitParameterAnnotation(i, an.desc, false)); 397 } 398 } 399 400 methodNode.visitEnd(); 401 402 } 403 404 private static void removeDuplicatedAnnotations(MethodNode node) 405 { 406 407 removeDuplicatedAnnotations(node.visibleAnnotations); 408 removeDuplicatedAnnotations(node.invisibleAnnotations); 409 410 if (node.visibleParameterAnnotations != null) 411 { 412 for (List<AnnotationNode> list : node.visibleParameterAnnotations) 413 { 414 removeDuplicatedAnnotations(list); 415 } 416 } 417 418 if (node.invisibleParameterAnnotations != null) 419 { 420 for (List<AnnotationNode> list : node.invisibleParameterAnnotations) 421 { 422 removeDuplicatedAnnotations(list); 423 } 424 } 425 426 } 427 428 private static void removeDuplicatedAnnotations(ClassNode node) 429 { 430 removeDuplicatedAnnotations(node.visibleAnnotations, true); 431 removeDuplicatedAnnotations(node.invisibleAnnotations, true); 432 } 433 434 private static void removeDuplicatedAnnotations(List<AnnotationNode> list) { 435 removeDuplicatedAnnotations(list, false); 436 } 437 438 private static void removeDuplicatedAnnotations(List<AnnotationNode> list, boolean reverse) { 439 440 if (list != null) 441 { 442 443 final Set<String> annotations = new HashSet<String>(); 444 final List<AnnotationNode> toBeRemoved = new ArrayList<AnnotationNode>(); 445 final List<AnnotationNode> toBeIterated; 446 447 if (reverse) 448 { 449 toBeIterated = new ArrayList<AnnotationNode>(list); 450 Collections.reverse(toBeIterated); 451 } 452 else { 453 toBeIterated = list; 454 } 455 456 for (AnnotationNode annotationNode : toBeIterated) 457 { 458 if (annotations.contains(annotationNode.desc)) 459 { 460 toBeRemoved.add(annotationNode); 461 } 462 else 463 { 464 annotations.add(annotationNode.desc); 465 } 466 } 467 468 for (AnnotationNode annotationNode : toBeRemoved) 469 { 470 list.remove(annotationNode); 471 } 472 473 } 474 475 } 476 477 private static String getParametersDesc(MethodNode methodNode) { 478 return methodNode.desc.substring(methodNode.desc.indexOf('(') + 1, methodNode.desc.lastIndexOf(')')); 479 } 480 481 private static MethodNode findExactMatchMethod(MethodNode methodNode, ClassNode source) { 482 483 MethodNode found = null; 484 485 final String methodDescription = getParametersDesc(methodNode); 486 487 for (MethodNode implementationMethodNode : source.methods) 488 { 489 490 final String implementationMethodDescription = getParametersDesc(implementationMethodNode); 491 if (methodNode.name.equals(implementationMethodNode.name) && 492 // We don't want synthetic methods. 493 ((implementationMethodNode.access & Opcodes.ACC_SYNTHETIC) == 0) 494 && (methodDescription.equals(implementationMethodDescription))) 495 { 496 found = implementationMethodNode; 497 break; 498 } 499 } 500 501 return found; 502 503 } 504 505 private static List<Class> getJavaParameterTypes(MethodNode methodNode) { 506 final ClassLoader classLoader = PlasticInternalUtils.class.getClassLoader(); 507 Type[] parameterTypes = Type.getArgumentTypes(methodNode.desc); 508 List<Class> list = new ArrayList<Class>(); 509 for (Type type : parameterTypes) 510 { 511 try 512 { 513 list.add(PlasticInternalUtils.toClass(classLoader, type.getClassName())); 514 } 515 catch (ClassNotFoundException e) 516 { 517 throw new RuntimeException(e); // shouldn't happen anyway 518 } 519 } 520 return list; 521 } 522 523 /** 524 * Returns the first method which matches the given methodNode. 525 * FIXME: this may not find the correct method if the correct one is declared after 526 * another in which all parameters are supertypes of the parameters of methodNode. 527 * To solve this, we would need to dig way deeper than we have time for this. 528 * @param methodNode 529 * @param classNode 530 * @return 531 */ 532 private static MethodNode findGenericMethod(MethodNode methodNode, ClassNode classNode) 533 { 534 535 MethodNode found = null; 536 537 List<Class> parameterTypes = getJavaParameterTypes(methodNode); 538 539 for (MethodNode implementationMethodNode : classNode.methods) 540 { 541 542 if (methodNode.name.equals(implementationMethodNode.name)) 543 { 544 545 final List<Class> implementationParameterTypes = getJavaParameterTypes(implementationMethodNode); 546 547 if (parameterTypes.size() == implementationParameterTypes.size()) 548 { 549 550 boolean matches = true; 551 for (int i = 0; i < parameterTypes.size(); i++) 552 { 553 final Class implementationParameterType = implementationParameterTypes.get(i); 554 final Class parameterType = parameterTypes.get(i); 555 if (!parameterType.isAssignableFrom(implementationParameterType)) { 556 matches = false; 557 break; 558 } 559 560 } 561 562 if (matches && !isBridge(implementationMethodNode)) 563 { 564 found = implementationMethodNode; 565 break; 566 } 567 568 } 569 570 } 571 572 } 573 574 return found; 575 576 } 577 578 private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, ClassNode source) 579 { 580 if (source != null) 581 { 582 583 MethodNode candidate = findExactMatchMethod(methodNode, source); 584 585 final String parametersDesc = getParametersDesc(methodNode); 586 587 // candidate will be null when the method has generic parameters 588 if (candidate == null && parametersDesc.trim().length() > 0) 589 { 590 candidate = findGenericMethod(methodNode, source); 591 } 592 593 if (candidate != null) 594 { 595 addMethodAndParameterAnnotationsFromExistingClass(methodNode, candidate); 596 } 597 598 } 599 600 } 601 602 /** 603 * Tells whether a given method is a bridge one or not. 604 * Notice the flag for bridge method is the same as volatile field. Java 6 doesn't have 605 * Modifiers.isBridge(), so we use a workaround. 606 */ 607 private static boolean isBridge(MethodNode methodNode) { 608 return Modifier.isVolatile(methodNode.access); 609 } 610 611 @Override 612 public PlasticClass proxyInterface(Class interfaceType, PlasticField field) 613 { 614 check(); 615 616 assert field != null; 617 618 introduceInterface(interfaceType); 619 620 for (Method m : interfaceType.getMethods()) 621 { 622 introduceMethod(m).delegateTo(field); 623 } 624 625 return this; 626 } 627 628 @Override 629 public ClassInstantiator createInstantiator() 630 { 631 lock(); 632 633 addClassAnnotations(implementationClassNode); 634 removeDuplicatedAnnotations(classNode); 635 636 createShimIfNeeded(); 637 638 interceptFieldAccess(); 639 640 rewriteAdvisedMethods(); 641 642 completeConstructor(); 643 644 transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext); 645 646 return createInstantiatorFromClass(transformedClass); 647 } 648 649 private void addClassAnnotations(ClassNode otherClassNode) 650 { 651 // Copy annotations from implementation if available. 652 // Code adapted from ClassNode.accept(), as we just want to copy 653 // the annotations and nothing more. 654 if (otherClassNode != null) 655 { 656 657 int i, n; 658 n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size(); 659 for (i = 0; i < n; ++i) 660 { 661 AnnotationNode an = otherClassNode.visibleAnnotations.get(i); 662 an.accept(classNode.visitAnnotation(an.desc, true)); 663 } 664 n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size(); 665 for (i = 0; i < n; ++i) 666 { 667 AnnotationNode an = otherClassNode.invisibleAnnotations.get(i); 668 an.accept(classNode.visitAnnotation(an.desc, false)); 669 } 670 671 } 672 } 673 674 private ClassInstantiator createInstantiatorFromClass(Class clazz) 675 { 676 try 677 { 678 Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class); 679 680 return new ClassInstantiatorImpl(clazz, ctor, staticContext); 681 } catch (Exception ex) 682 { 683 throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s", 684 clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex); 685 } 686 } 687 688 private void completeConstructor() 689 { 690 if (originalConstructor != null) 691 { 692 convertOriginalConstructorToMethod(); 693 } 694 695 invokeCallbacks(); 696 697 constructorBuilder.returnResult(); 698 699 classNode.methods.add(newConstructor); 700 } 701 702 private void invokeCallbacks() 703 { 704 for (ConstructorCallback callback : constructorCallbacks) 705 { 706 invokeCallback(callback); 707 } 708 } 709 710 private void invokeCallback(ConstructorCallback callback) 711 { 712 int index = staticContext.store(callback); 713 714 // First, load the callback 715 716 constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName()); 717 718 // Load this and the InstanceContext 719 constructorBuilder.loadThis().loadArgument(1); 720 721 constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD); 722 } 723 724 725 /** 726 * Convert the original constructor into a private method invoked from the 727 * generated constructor. 728 */ 729 private void convertOriginalConstructorToMethod() 730 { 731 String initializerName = makeUnique(methodNames, "initializeInstance"); 732 733 int originalAccess = originalConstructor.access; 734 735 originalConstructor.access = ACC_PRIVATE; 736 originalConstructor.name = initializerName; 737 738 stripOutSuperConstructorCall(originalConstructor); 739 740 constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName); 741 742 // And replace it with a constructor that throws an exception 743 744 MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, 745 null); 746 747 newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage()); 748 749 classNode.methods.add(replacementConstructor); 750 } 751 752 private void stripOutSuperConstructorCall(MethodNode cons) 753 { 754 InsnList ins = cons.instructions; 755 756 ListIterator li = ins.iterator(); 757 758 // Look for the ALOAD 0 (i.e., push this on the stack) 759 while (li.hasNext()) 760 { 761 AbstractInsnNode node = (AbstractInsnNode) li.next(); 762 763 if (node.getOpcode() == ALOAD) 764 { 765 VarInsnNode varNode = (VarInsnNode) node; 766 767 assert varNode.var == 0; 768 769 // Remove the ALOAD 770 li.remove(); 771 break; 772 } 773 } 774 775 // Look for the call to the super-class, an INVOKESPECIAL 776 while (li.hasNext()) 777 { 778 AbstractInsnNode node = (AbstractInsnNode) li.next(); 779 780 if (node.getOpcode() == INVOKESPECIAL) 781 { 782 MethodInsnNode mnode = (MethodInsnNode) node; 783 784 assert mnode.owner.equals(classNode.superName); 785 assert mnode.name.equals(CONSTRUCTOR_NAME); 786 assert mnode.desc.equals(cons.desc); 787 788 li.remove(); 789 return; 790 } 791 } 792 793 throw new AssertionError("Could not convert constructor to simple method."); 794 } 795 796 @Override 797 public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType) 798 { 799 check(); 800 801 List<PlasticField> result = getAllFields(); 802 803 Iterator<PlasticField> iterator = result.iterator(); 804 805 while (iterator.hasNext()) 806 { 807 PlasticField plasticField = iterator.next(); 808 809 if (!plasticField.hasAnnotation(annotationType)) 810 iterator.remove(); 811 } 812 813 return result; 814 } 815 816 @Override 817 public List<PlasticField> getAllFields() 818 { 819 check(); 820 821 return new ArrayList<PlasticField>(fields); 822 } 823 824 @Override 825 public List<PlasticField> getUnclaimedFields() 826 { 827 check(); 828 829 // Initially null, and set back to null by PlasticField.claim(). 830 831 if (unclaimedFields == null) 832 { 833 unclaimedFields = new ArrayList<PlasticField>(fields.size()); 834 835 for (PlasticField f : fields) 836 { 837 if (!f.isClaimed()) 838 unclaimedFields.add(f); 839 } 840 } 841 842 return unclaimedFields; 843 } 844 845 @Override 846 public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes, 847 String[] exceptionTypes) 848 { 849 check(); 850 851 assert PlasticInternalUtils.isNonBlank(typeName); 852 assert PlasticInternalUtils.isNonBlank(suggestedName); 853 854 String name = makeUnique(methodNames, suggestedName); 855 856 MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null, 857 exceptionTypes); 858 859 return introduceMethod(description); 860 } 861 862 @Override 863 public PlasticField introduceField(String className, String suggestedName) 864 { 865 check(); 866 867 assert PlasticInternalUtils.isNonBlank(className); 868 assert PlasticInternalUtils.isNonBlank(suggestedName); 869 870 String name = makeUnique(fieldNames, suggestedName); 871 872 // No signature and no initial value 873 874 FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null); 875 876 classNode.fields.add(fieldNode); 877 878 fieldNames.add(name); 879 880 PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode); 881 882 return newField; 883 } 884 885 @Override 886 public PlasticField introduceField(Class fieldType, String suggestedName) 887 { 888 assert fieldType != null; 889 890 return introduceField(nameCache.toTypeName(fieldType), suggestedName); 891 } 892 893 String makeUnique(Set<String> values, String input) 894 { 895 return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input; 896 } 897 898 @Override 899 public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType) 900 { 901 check(); 902 903 List<PlasticMethod> result = getMethods(); 904 Iterator<PlasticMethod> iterator = result.iterator(); 905 906 while (iterator.hasNext()) 907 { 908 PlasticMethod method = iterator.next(); 909 910 if (!method.hasAnnotation(annotationType)) 911 iterator.remove(); 912 } 913 914 return result; 915 } 916 917 @Override 918 public List<PlasticMethod> getMethods() 919 { 920 check(); 921 922 return new ArrayList<PlasticMethod>(methods); 923 } 924 925 @Override 926 public PlasticMethod introduceMethod(MethodDescription description) 927 { 928 check(); 929 930 if (Modifier.isAbstract(description.modifiers)) 931 { 932 description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT); 933 } 934 935 PlasticMethod result = description2method.get(description); 936 937 if (result == null) 938 { 939 result = createNewMethod(description); 940 941 description2method.put(description, result); 942 } 943 944 methodNames.add(description.methodName); 945 946 // Note that is it not necessary to add the new MethodNode to 947 // fieldTransformMethods (the default implementations provided by introduceMethod() do not 948 // ever access instance fields) ... unless the caller invokes changeImplementation(). 949 950 return result; 951 } 952 953 @Override 954 public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback) 955 { 956 check(); 957 958 // TODO: optimize this so that a default implementation is not created. 959 960 return introduceMethod(description).changeImplementation(callback); 961 } 962 963 @Override 964 public PlasticMethod introduceMethod(Method method) 965 { 966 check(); 967 968 return introduceMethod(new MethodDescription(method)); 969 } 970 971 void addMethod(MethodNode methodNode) 972 { 973 classNode.methods.add(methodNode); 974 975 methodNames.add(methodNode.name); 976 977 if (isInheritableMethod(methodNode)) 978 { 979 inheritanceData.addMethod(methodNode.name, methodNode.desc); 980 } 981 } 982 983 private PlasticMethod createNewMethod(MethodDescription description) 984 { 985 if (Modifier.isStatic(description.modifiers)) 986 throw new IllegalArgumentException(String.format( 987 "Unable to introduce method '%s' into class %s: introduced methods may not be static.", 988 description, className)); 989 990 String desc = nameCache.toDesc(description); 991 992 String[] exceptions = new String[description.checkedExceptionTypes.length]; 993 for (int i = 0; i < exceptions.length; i++) 994 { 995 exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]); 996 } 997 998 MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc, 999 description.genericSignature, exceptions); 1000 boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc); 1001 1002 if (!isOverride) 1003 { 1004 addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode); 1005 addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode); 1006 removeDuplicatedAnnotations(methodNode); 1007 } 1008 1009 if (isOverride) 1010 createOverrideOfBaseClassImpl(description, methodNode); 1011 else 1012 createNewMethodImpl(description, methodNode); 1013 1014 addMethod(methodNode); 1015 1016 return new PlasticMethodImpl(this, methodNode); 1017 } 1018 1019 private boolean isDefaultMethod(Method method) 1020 { 1021 return method.getDeclaringClass().isInterface() && 1022 (method.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_ABSTRACT)) == Opcodes.ACC_PUBLIC; 1023 } 1024 1025 private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode) 1026 { 1027 newBuilder(methodDescription, methodNode).returnDefaultValue(); 1028 } 1029 1030 private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode) 1031 { 1032 InstructionBuilder builder = newBuilder(methodDescription, methodNode); 1033 1034 builder.loadThis(); 1035 builder.loadArguments(); 1036 builder.invokeSpecial(superClassName, methodDescription); 1037 builder.returnResult(); 1038 } 1039 1040 /** 1041 * Iterates over all non-introduced methods, including the original constructor. For each 1042 * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field, 1043 * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim} 1044 * for the class has been created, as the shim may create methods that contain references to fields that may be 1045 * subject to field access interception. 1046 */ 1047 private void interceptFieldAccess() 1048 { 1049 for (MethodNode node : fieldTransformMethods) 1050 { 1051 // Intercept field access inside the method, tracking which access methods 1052 // are actually used by removing them from accessMethods 1053 1054 interceptFieldAccess(node); 1055 } 1056 } 1057 1058 /** 1059 * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so 1060 * a shim class must be created to facilitate read/write access to fields, or invocation of methods. 1061 */ 1062 private void createShimIfNeeded() 1063 { 1064 if (shimFields.isEmpty() && shimMethods.isEmpty()) 1065 return; 1066 1067 PlasticClassHandleShim shim = createShimInstance(); 1068 1069 installShim(shim); 1070 } 1071 1072 public void installShim(PlasticClassHandleShim shim) 1073 { 1074 for (PlasticFieldImpl f : shimFields) 1075 { 1076 f.installShim(shim); 1077 } 1078 1079 for (PlasticMethodImpl m : shimMethods) 1080 { 1081 m.installShim(shim); 1082 } 1083 } 1084 1085 public PlasticClassHandleShim createShimInstance() 1086 { 1087 String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID()); 1088 1089 ClassNode shimClassNode = new ClassNode(); 1090 1091 shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME, 1092 null); 1093 1094 implementConstructor(shimClassNode); 1095 1096 if (!shimFields.isEmpty()) 1097 { 1098 implementShimGet(shimClassNode); 1099 implementShimSet(shimClassNode); 1100 } 1101 1102 if (!shimMethods.isEmpty()) 1103 { 1104 implementShimInvoke(shimClassNode); 1105 } 1106 1107 return instantiateShim(shimClassNode); 1108 } 1109 1110 private void implementConstructor(ClassNode shimClassNode) 1111 { 1112 MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null); 1113 1114 InstructionBuilder builder = newBuilder(mn); 1115 1116 builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult(); 1117 1118 shimClassNode.methods.add(mn); 1119 1120 } 1121 1122 private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode) 1123 { 1124 try 1125 { 1126 Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode); 1127 1128 return (PlasticClassHandleShim) shimClass.newInstance(); 1129 } catch (Exception ex) 1130 { 1131 throw new RuntimeException( 1132 String.format("Unable to instantiate shim class %s for plastic class %s: %s", 1133 PlasticInternalUtils.toClassName(shimClassNode.name), className, 1134 PlasticInternalUtils.toMessage(ex)), ex); 1135 } 1136 } 1137 1138 private void implementShimGet(ClassNode shimClassNode) 1139 { 1140 MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null); 1141 1142 InstructionBuilder builder = newBuilder(mn); 1143 1144 // Arg 0 is the target instance 1145 // Arg 1 is the index 1146 1147 builder.loadArgument(0).checkcast(className); 1148 builder.loadArgument(1); 1149 1150 builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() 1151 { 1152 @Override 1153 public void doSwitch(SwitchBlock block) 1154 { 1155 for (PlasticFieldImpl f : shimFields) 1156 { 1157 f.extendShimGet(block); 1158 } 1159 } 1160 }); 1161 1162 shimClassNode.methods.add(mn); 1163 } 1164 1165 private void implementShimSet(ClassNode shimClassNode) 1166 { 1167 MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null); 1168 1169 InstructionBuilder builder = newBuilder(mn); 1170 1171 // Arg 0 is the target instance 1172 // Arg 1 is the index 1173 // Arg 2 is the new value 1174 1175 builder.loadArgument(0).checkcast(className); 1176 builder.loadArgument(2); 1177 1178 builder.loadArgument(1); 1179 1180 builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() 1181 { 1182 @Override 1183 public void doSwitch(SwitchBlock block) 1184 { 1185 for (PlasticFieldImpl f : shimFields) 1186 { 1187 f.extendShimSet(block); 1188 } 1189 } 1190 }); 1191 1192 builder.returnResult(); 1193 1194 shimClassNode.methods.add(mn); 1195 } 1196 1197 private void implementShimInvoke(ClassNode shimClassNode) 1198 { 1199 MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null, 1200 null); 1201 1202 InstructionBuilder builder = newBuilder(mn); 1203 1204 // Arg 0 is the target instance 1205 // Arg 1 is the index 1206 // Arg 2 is the object array of parameters 1207 1208 builder.loadArgument(0).checkcast(className); 1209 1210 builder.loadArgument(1); 1211 1212 builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback() 1213 { 1214 @Override 1215 public void doSwitch(SwitchBlock block) 1216 { 1217 for (PlasticMethodImpl m : shimMethods) 1218 { 1219 m.extendShimInvoke(block); 1220 } 1221 } 1222 }); 1223 1224 shimClassNode.methods.add(mn); 1225 } 1226 1227 private void rewriteAdvisedMethods() 1228 { 1229 for (PlasticMethodImpl method : advisedMethods) 1230 { 1231 method.rewriteMethodForAdvice(); 1232 } 1233 } 1234 1235 private void interceptFieldAccess(MethodNode methodNode) 1236 { 1237 InsnList insns = methodNode.instructions; 1238 1239 ListIterator it = insns.iterator(); 1240 1241 while (it.hasNext()) 1242 { 1243 AbstractInsnNode node = (AbstractInsnNode) it.next(); 1244 1245 int opcode = node.getOpcode(); 1246 1247 if (opcode != GETFIELD && opcode != PUTFIELD) 1248 { 1249 continue; 1250 } 1251 1252 FieldInsnNode fnode = (FieldInsnNode) node; 1253 1254 FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD); 1255 1256 if (instrumentation == null) 1257 { 1258 continue; 1259 } 1260 1261 // Replace the field access node with the appropriate method invocation. 1262 1263 insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription)); 1264 1265 it.remove(); 1266 } 1267 } 1268 1269 private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead) 1270 { 1271 // First look in the local fieldInstrumentations, which contains private field instrumentations 1272 // (as well as non-private ones). 1273 1274 String searchStart = node.owner; 1275 1276 if (searchStart.equals(classNode.name)) 1277 { 1278 FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead); 1279 1280 if (result != null) 1281 { 1282 return result; 1283 } 1284 1285 // Slight optimization: start the search in the super-classes' fields, since we've already 1286 // checked this classes fields. 1287 1288 searchStart = classNode.superName; 1289 } 1290 1291 return pool.getFieldInstrumentation(searchStart, node.name, forRead); 1292 } 1293 1294 String getInstanceContextFieldName() 1295 { 1296 if (instanceContextFieldName == null) 1297 { 1298 instanceContextFieldName = makeUnique(fieldNames, "instanceContext"); 1299 1300 // TODO: We could use a protected field and only initialize 1301 // it once, in the first base class where it is needed, though that raises the possibilities 1302 // of name conflicts (a subclass might introduce a field with a conflicting name). 1303 1304 FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC, 1305 null, null); 1306 1307 classNode.fields.add(node); 1308 1309 // Extend the constructor to store the context in a field. 1310 1311 constructorBuilder.loadThis().loadArgument(1) 1312 .putField(className, instanceContextFieldName, InstanceContext.class); 1313 } 1314 1315 return instanceContextFieldName; 1316 } 1317 1318 /** 1319 * Creates a new private final field and initializes its value (using the StaticContext). 1320 */ 1321 String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType, 1322 Object injectedFieldValue) 1323 { 1324 String name = makeUnique(fieldNames, suggestedFieldName); 1325 1326 FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null); 1327 1328 classNode.fields.add(field); 1329 1330 initializeFieldFromStaticContext(name, fieldType, injectedFieldValue); 1331 1332 return name; 1333 } 1334 1335 /** 1336 * Initializes a field from the static context. The injected value is added to the static 1337 * context and the class constructor updated to assign the value from the context (which includes casting and 1338 * possibly unboxing). 1339 */ 1340 void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue) 1341 { 1342 int index = staticContext.store(injectedFieldValue); 1343 1344 // Although it feels nicer to do the loadThis() later and then swap(), that breaks 1345 // on primitive longs and doubles, so its just easier to do the loadThis() first 1346 // so its at the right place on the stack for the putField(). 1347 1348 constructorBuilder.loadThis(); 1349 1350 constructorBuilder.loadArgument(0).loadConstant(index); 1351 constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD); 1352 constructorBuilder.castOrUnbox(fieldType); 1353 1354 constructorBuilder.putField(className, fieldName, fieldType); 1355 } 1356 1357 void pushInstanceContextFieldOntoStack(InstructionBuilder builder) 1358 { 1359 builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class); 1360 } 1361 1362 @Override 1363 public PlasticClass getPlasticClass() 1364 { 1365 return this; 1366 } 1367 1368 @Override 1369 public Class<?> getTransformedClass() 1370 { 1371 if (transformedClass == null) 1372 throw new IllegalStateException(String.format( 1373 "Transformed class %s is not yet available because the transformation is not yet complete.", 1374 className)); 1375 1376 return transformedClass; 1377 } 1378 1379 private boolean isInheritableMethod(MethodNode node) 1380 { 1381 return !Modifier.isPrivate(node.access); 1382 } 1383 1384 @Override 1385 public String getClassName() 1386 { 1387 return className; 1388 } 1389 1390 InstructionBuilderImpl newBuilder(MethodNode mn) 1391 { 1392 return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn); 1393 } 1394 1395 InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn) 1396 { 1397 return new InstructionBuilderImpl(description, mn, nameCache); 1398 } 1399 1400 @Override 1401 public Set<PlasticMethod> introduceInterface(Class interfaceType) 1402 { 1403 check(); 1404 1405 assert interfaceType != null; 1406 1407 if (!interfaceType.isInterface()) 1408 throw new IllegalArgumentException(String.format( 1409 "Class %s is not an interface; ony interfaces may be introduced.", interfaceType.getName())); 1410 1411 String interfaceName = nameCache.toInternalName(interfaceType); 1412 1413 try 1414 { 1415 interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader()); 1416 } catch (IOException e) 1417 { 1418 throw new RuntimeException(e); 1419 } 1420 1421 if (!inheritanceData.isInterfaceImplemented(interfaceName)) 1422 { 1423 classNode.interfaces.add(interfaceName); 1424 inheritanceData.addInterface(interfaceName); 1425 } 1426 1427 addClassAnnotations(interfaceClassNode); 1428 1429 Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>(); 1430 1431 for (Method m : interfaceType.getMethods()) 1432 { 1433 MethodDescription description = new MethodDescription(m); 1434 1435 if (!isMethodImplemented(description) && !isDefaultMethod(m)) 1436 { 1437 introducedMethods.add(introduceMethod(m)); 1438 } 1439 } 1440 1441 interfaceClassNode = null; 1442 1443 return introducedMethods; 1444 } 1445 1446 @Override 1447 public PlasticClass addToString(final String toStringValue) 1448 { 1449 check(); 1450 1451 if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION)) 1452 { 1453 introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback() 1454 { 1455 @Override 1456 public void doBuild(InstructionBuilder builder) 1457 { 1458 builder.loadConstant(toStringValue).returnResult(); 1459 } 1460 }); 1461 } 1462 1463 return this; 1464 } 1465 1466 @Override 1467 public boolean isMethodImplemented(MethodDescription description) 1468 { 1469 return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description)); 1470 } 1471 1472 @Override 1473 public boolean isInterfaceImplemented(Class interfaceType) 1474 { 1475 assert interfaceType != null; 1476 assert interfaceType.isInterface(); 1477 1478 String interfaceName = nameCache.toInternalName(interfaceType); 1479 1480 return inheritanceData.isInterfaceImplemented(interfaceName); 1481 } 1482 1483 @Override 1484 public String getSuperClassName() 1485 { 1486 return superClassName; 1487 } 1488 1489 @Override 1490 public PlasticClass onConstruct(ConstructorCallback callback) 1491 { 1492 check(); 1493 1494 assert callback != null; 1495 1496 constructorCallbacks.add(callback); 1497 1498 return this; 1499 } 1500 1501 void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method) 1502 { 1503 FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); 1504 1505 fieldInstrumentations.write.put(fieldName, fi); 1506 1507 if (!(proxy || privateField)) 1508 { 1509 pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi); 1510 } 1511 } 1512 1513 void redirectFieldRead(String fieldName, boolean privateField, MethodNode method) 1514 { 1515 FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); 1516 1517 fieldInstrumentations.read.put(fieldName, fi); 1518 1519 if (!(proxy || privateField)) 1520 { 1521 pool.setFieldReadInstrumentation(classNode.name, fieldName, fi); 1522 } 1523 } 1524 1525 @Override 1526 public String toString() 1527 { 1528 return String.format("PlasticClassImpl[%s]", className); 1529 } 1530}