001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.spel; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.Method; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Stack; 024 025import org.springframework.asm.ClassWriter; 026import org.springframework.asm.MethodVisitor; 027import org.springframework.asm.Opcodes; 028import org.springframework.util.Assert; 029 030/** 031 * Manages the class being generated by the compilation process. 032 * 033 * <p>Records intermediate compilation state as the bytecode is generated. 034 * Also includes various bytecode generation helper functions. 035 * 036 * @author Andy Clement 037 * @author Juergen Hoeller 038 * @since 4.1 039 */ 040public class CodeFlow implements Opcodes { 041 042 /** 043 * Name of the class being generated. Typically used when generating code 044 * that accesses freshly generated fields on the generated type. 045 */ 046 private final String className; 047 048 /** 049 * The current class being generated. 050 */ 051 private final ClassWriter classWriter; 052 053 /** 054 * Record the type of what is on top of the bytecode stack (i.e. the type of the 055 * output from the previous expression component). New scopes are used to evaluate 056 * sub-expressions like the expressions for the argument values in a method invocation 057 * expression. 058 */ 059 private final Stack<ArrayList<String>> compilationScopes; 060 061 /** 062 * As SpEL ast nodes are called to generate code for the main evaluation method 063 * they can register to add a field to this class. Any registered FieldAdders 064 * will be called after the main evaluation function has finished being generated. 065 */ 066 private List<FieldAdder> fieldAdders; 067 068 /** 069 * As SpEL ast nodes are called to generate code for the main evaluation method 070 * they can register to add code to a static initializer in the class. Any 071 * registered ClinitAdders will be called after the main evaluation function 072 * has finished being generated. 073 */ 074 private List<ClinitAdder> clinitAdders; 075 076 /** 077 * When code generation requires holding a value in a class level field, this 078 * is used to track the next available field id (used as a name suffix). 079 */ 080 private int nextFieldId = 1; 081 082 /** 083 * When code generation requires an intermediate variable within a method, 084 * this method records the next available variable (variable 0 is 'this'). 085 */ 086 private int nextFreeVariableId = 1; 087 088 089 /** 090 * Construct a new {@code CodeFlow} for the given class. 091 * @param className the name of the class 092 * @param classWriter the corresponding ASM {@code ClassWriter} 093 */ 094 public CodeFlow(String className, ClassWriter classWriter) { 095 this.className = className; 096 this.classWriter = classWriter; 097 this.compilationScopes = new Stack<ArrayList<String>>(); 098 this.compilationScopes.add(new ArrayList<String>()); 099 } 100 101 102 /** 103 * Push the byte code to load the target (i.e. what was passed as the first argument 104 * to CompiledExpression.getValue(target, context)) 105 * @param mv the visitor into which the load instruction should be inserted 106 */ 107 public void loadTarget(MethodVisitor mv) { 108 mv.visitVarInsn(ALOAD, 1); 109 } 110 111 /** 112 * Push the bytecode to load the EvaluationContext (the second parameter passed to 113 * the compiled expression method). 114 * @param mv the visitor into which the load instruction should be inserted 115 * @since 4.3.4 116 */ 117 public void loadEvaluationContext(MethodVisitor mv) { 118 mv.visitVarInsn(ALOAD, 2); 119 } 120 121 /** 122 * Record the descriptor for the most recently evaluated expression element. 123 * @param descriptor type descriptor for most recently evaluated element 124 */ 125 public void pushDescriptor(String descriptor) { 126 Assert.notNull(descriptor, "Descriptor must not be null"); 127 this.compilationScopes.peek().add(descriptor); 128 } 129 130 /** 131 * Enter a new compilation scope, usually due to nested expression evaluation. For 132 * example when the arguments for a method invocation expression are being evaluated, 133 * each argument will be evaluated in a new scope. 134 */ 135 public void enterCompilationScope() { 136 this.compilationScopes.push(new ArrayList<String>()); 137 } 138 139 /** 140 * Exit a compilation scope, usually after a nested expression has been evaluated. For 141 * example after an argument for a method invocation has been evaluated this method 142 * returns us to the previous (outer) scope. 143 */ 144 public void exitCompilationScope() { 145 this.compilationScopes.pop(); 146 } 147 148 /** 149 * Return the descriptor for the item currently on top of the stack (in the current scope). 150 */ 151 public String lastDescriptor() { 152 ArrayList<String> scopes = this.compilationScopes.peek(); 153 return (!scopes.isEmpty() ? scopes.get(scopes.size() - 1) : null); 154 } 155 156 /** 157 * If the codeflow shows the last expression evaluated to java.lang.Boolean then 158 * insert the necessary instructions to unbox that to a boolean primitive. 159 * @param mv the visitor into which new instructions should be inserted 160 */ 161 public void unboxBooleanIfNecessary(MethodVisitor mv) { 162 if ("Ljava/lang/Boolean".equals(lastDescriptor())) { 163 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); 164 } 165 } 166 167 /** 168 * Called after the main expression evaluation method has been generated, this 169 * method will callback any registered FieldAdders or ClinitAdders to add any 170 * extra information to the class representing the compiled expression. 171 */ 172 public void finish() { 173 if (this.fieldAdders != null) { 174 for (FieldAdder fieldAdder : this.fieldAdders) { 175 fieldAdder.generateField(this.classWriter, this); 176 } 177 } 178 if (this.clinitAdders != null) { 179 MethodVisitor mv = this.classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "<clinit>", "()V", null, null); 180 mv.visitCode(); 181 this.nextFreeVariableId = 0; // to 0 because there is no 'this' in a clinit 182 for (ClinitAdder clinitAdder : this.clinitAdders) { 183 clinitAdder.generateCode(mv, this); 184 } 185 mv.visitInsn(RETURN); 186 mv.visitMaxs(0,0); // not supplied due to COMPUTE_MAXS 187 mv.visitEnd(); 188 } 189 } 190 191 /** 192 * Register a FieldAdder which will add a new field to the generated 193 * class to support the code produced by an ast nodes primary 194 * generateCode() method. 195 */ 196 public void registerNewField(FieldAdder fieldAdder) { 197 if (this.fieldAdders == null) { 198 this.fieldAdders = new ArrayList<FieldAdder>(); 199 } 200 this.fieldAdders.add(fieldAdder); 201 } 202 203 /** 204 * Register a ClinitAdder which will add code to the static 205 * initializer in the generated class to support the code 206 * produced by an ast nodes primary generateCode() method. 207 */ 208 public void registerNewClinit(ClinitAdder clinitAdder) { 209 if (this.clinitAdders == null) { 210 this.clinitAdders = new ArrayList<ClinitAdder>(); 211 } 212 this.clinitAdders.add(clinitAdder); 213 } 214 215 public int nextFieldId() { 216 return this.nextFieldId++; 217 } 218 219 public int nextFreeVariableId() { 220 return this.nextFreeVariableId++; 221 } 222 223 public String getClassName() { 224 return this.className; 225 } 226 227 @Deprecated 228 public String getClassname() { 229 return this.className; 230 } 231 232 233 /** 234 * Insert any necessary cast and value call to convert from a boxed type to a 235 * primitive value. 236 * @param mv the method visitor into which instructions should be inserted 237 * @param ch the primitive type desired as output 238 * @param stackDescriptor the descriptor of the type on top of the stack 239 */ 240 public static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) { 241 switch (ch) { 242 case 'Z': 243 if (!stackDescriptor.equals("Ljava/lang/Boolean")) { 244 mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); 245 } 246 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); 247 break; 248 case 'B': 249 if (!stackDescriptor.equals("Ljava/lang/Byte")) { 250 mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); 251 } 252 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false); 253 break; 254 case 'C': 255 if (!stackDescriptor.equals("Ljava/lang/Character")) { 256 mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); 257 } 258 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false); 259 break; 260 case 'D': 261 if (!stackDescriptor.equals("Ljava/lang/Double")) { 262 mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); 263 } 264 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); 265 break; 266 case 'F': 267 if (!stackDescriptor.equals("Ljava/lang/Float")) { 268 mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); 269 } 270 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false); 271 break; 272 case 'I': 273 if (!stackDescriptor.equals("Ljava/lang/Integer")) { 274 mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); 275 } 276 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); 277 break; 278 case 'J': 279 if (!stackDescriptor.equals("Ljava/lang/Long")) { 280 mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); 281 } 282 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); 283 break; 284 case 'S': 285 if (!stackDescriptor.equals("Ljava/lang/Short")) { 286 mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); 287 } 288 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false); 289 break; 290 default: 291 throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'"); 292 } 293 } 294 295 /** 296 * For numbers, use the appropriate method on the number to convert it to the primitive type requested. 297 * @param mv the method visitor into which instructions should be inserted 298 * @param targetDescriptor the primitive type desired as output 299 * @param stackDescriptor the descriptor of the type on top of the stack 300 */ 301 public static void insertUnboxNumberInsns(MethodVisitor mv, char targetDescriptor, String stackDescriptor) { 302 switch (targetDescriptor) { 303 case 'D': 304 if (stackDescriptor.equals("Ljava/lang/Object")) { 305 mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); 306 } 307 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "doubleValue", "()D", false); 308 break; 309 case 'F': 310 if (stackDescriptor.equals("Ljava/lang/Object")) { 311 mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); 312 } 313 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "floatValue", "()F", false); 314 break; 315 case 'J': 316 if (stackDescriptor.equals("Ljava/lang/Object")) { 317 mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); 318 } 319 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "longValue", "()J", false); 320 break; 321 case 'I': 322 if (stackDescriptor.equals("Ljava/lang/Object")) { 323 mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); 324 } 325 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false); 326 break; 327 // does not handle Z, B, C, S 328 default: 329 throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + targetDescriptor + "'"); 330 } 331 } 332 333 /** 334 * Insert any necessary numeric conversion bytecodes based upon what is on the stack and the desired target type. 335 * @param mv the method visitor into which instructions should be placed 336 * @param targetDescriptor the (primitive) descriptor of the target type 337 * @param stackDescriptor the descriptor of the operand on top of the stack 338 */ 339 public static void insertAnyNecessaryTypeConversionBytecodes(MethodVisitor mv, char targetDescriptor, String stackDescriptor) { 340 if (CodeFlow.isPrimitive(stackDescriptor)) { 341 char stackTop = stackDescriptor.charAt(0); 342 if (stackTop == 'I' || stackTop == 'B' || stackTop == 'S' || stackTop == 'C') { 343 if (targetDescriptor == 'D') { 344 mv.visitInsn(I2D); 345 } 346 else if (targetDescriptor == 'F') { 347 mv.visitInsn(I2F); 348 } 349 else if (targetDescriptor == 'J') { 350 mv.visitInsn(I2L); 351 } 352 else if (targetDescriptor == 'I') { 353 // nop 354 } 355 else { 356 throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor); 357 } 358 } 359 else if (stackTop == 'J') { 360 if (targetDescriptor == 'D') { 361 mv.visitInsn(L2D); 362 } 363 else if (targetDescriptor == 'F') { 364 mv.visitInsn(L2F); 365 } 366 else if (targetDescriptor == 'J') { 367 // nop 368 } 369 else if (targetDescriptor == 'I') { 370 mv.visitInsn(L2I); 371 } 372 else { 373 throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor); 374 } 375 } 376 else if (stackTop == 'F') { 377 if (targetDescriptor == 'D') { 378 mv.visitInsn(F2D); 379 } 380 else if (targetDescriptor == 'F') { 381 // nop 382 } 383 else if (targetDescriptor == 'J') { 384 mv.visitInsn(F2L); 385 } 386 else if (targetDescriptor == 'I') { 387 mv.visitInsn(F2I); 388 } 389 else { 390 throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor); 391 } 392 } 393 else if (stackTop == 'D') { 394 if (targetDescriptor == 'D') { 395 // nop 396 } 397 else if (targetDescriptor == 'F') { 398 mv.visitInsn(D2F); 399 } 400 else if (targetDescriptor == 'J') { 401 mv.visitInsn(D2L); 402 } 403 else if (targetDescriptor == 'I') { 404 mv.visitInsn(D2I); 405 } 406 else { 407 throw new IllegalStateException("Cannot get from " + stackDescriptor + " to " + targetDescriptor); 408 } 409 } 410 } 411 } 412 413 414 /** 415 * Create the JVM signature descriptor for a method. This consists of the descriptors 416 * for the method parameters surrounded with parentheses, followed by the 417 * descriptor for the return type. Note the descriptors here are JVM descriptors, 418 * unlike the other descriptor forms the compiler is using which do not include the 419 * trailing semicolon. 420 * @param method the method 421 * @return a String signature descriptor (e.g. "(ILjava/lang/String;)V") 422 */ 423 public static String createSignatureDescriptor(Method method) { 424 Class<?>[] params = method.getParameterTypes(); 425 StringBuilder sb = new StringBuilder(); 426 sb.append("("); 427 for (Class<?> param : params) { 428 sb.append(toJvmDescriptor(param)); 429 } 430 sb.append(")"); 431 sb.append(toJvmDescriptor(method.getReturnType())); 432 return sb.toString(); 433 } 434 435 /** 436 * Create the JVM signature descriptor for a constructor. This consists of the 437 * descriptors for the constructor parameters surrounded with parentheses, followed by 438 * the descriptor for the return type, which is always "V". Note the 439 * descriptors here are JVM descriptors, unlike the other descriptor forms the 440 * compiler is using which do not include the trailing semicolon. 441 * @param ctor the constructor 442 * @return a String signature descriptor (e.g. "(ILjava/lang/String;)V") 443 */ 444 public static String createSignatureDescriptor(Constructor<?> ctor) { 445 Class<?>[] params = ctor.getParameterTypes(); 446 StringBuilder sb = new StringBuilder(); 447 sb.append("("); 448 for (Class<?> param : params) { 449 sb.append(toJvmDescriptor(param)); 450 } 451 sb.append(")V"); 452 return sb.toString(); 453 } 454 455 /** 456 * Determine the JVM descriptor for a specified class. Unlike the other descriptors 457 * used in the compilation process, this is the one the JVM wants, so this one 458 * includes any necessary trailing semicolon (e.g. Ljava/lang/String; rather than 459 * Ljava/lang/String) 460 * @param clazz a class 461 * @return the JVM descriptor for the class 462 */ 463 public static String toJvmDescriptor(Class<?> clazz) { 464 StringBuilder sb = new StringBuilder(); 465 if (clazz.isArray()) { 466 while (clazz.isArray()) { 467 sb.append("["); 468 clazz = clazz.getComponentType(); 469 } 470 } 471 if (clazz.isPrimitive()) { 472 if (clazz == Boolean.TYPE) { 473 sb.append('Z'); 474 } 475 else if (clazz == Byte.TYPE) { 476 sb.append('B'); 477 } 478 else if (clazz == Character.TYPE) { 479 sb.append('C'); 480 } 481 else if (clazz == Double.TYPE) { 482 sb.append('D'); 483 } 484 else if (clazz == Float.TYPE) { 485 sb.append('F'); 486 } 487 else if (clazz == Integer.TYPE) { 488 sb.append('I'); 489 } 490 else if (clazz == Long.TYPE) { 491 sb.append('J'); 492 } 493 else if (clazz == Short.TYPE) { 494 sb.append('S'); 495 } 496 else if (clazz == Void.TYPE) { 497 sb.append('V'); 498 } 499 } 500 else { 501 sb.append("L"); 502 sb.append(clazz.getName().replace('.', '/')); 503 sb.append(";"); 504 } 505 return sb.toString(); 506 } 507 508 /** 509 * Determine the descriptor for an object instance (or {@code null}). 510 * @param value an object (possibly {@code null}) 511 * @return the type descriptor for the object 512 * (descriptor is "Ljava/lang/Object" for {@code null} value) 513 */ 514 public static String toDescriptorFromObject(Object value) { 515 if (value == null) { 516 return "Ljava/lang/Object"; 517 } 518 else { 519 return toDescriptor(value.getClass()); 520 } 521 } 522 523 /** 524 * Determine whether the descriptor is for a boolean primitive or boolean reference type. 525 * @param descriptor type descriptor 526 * @return {@code true} if the descriptor is boolean compatible 527 */ 528 public static boolean isBooleanCompatible(String descriptor) { 529 return (descriptor != null && (descriptor.equals("Z") || descriptor.equals("Ljava/lang/Boolean"))); 530 } 531 532 /** 533 * Determine whether the descriptor is for a primitive type. 534 * @param descriptor type descriptor 535 * @return {@code true} if a primitive type 536 */ 537 public static boolean isPrimitive(String descriptor) { 538 return (descriptor != null && descriptor.length() == 1); 539 } 540 541 /** 542 * Determine whether the descriptor is for a primitive array (e.g. "[[I"). 543 * @param descriptor the descriptor for a possible primitive array 544 * @return {@code true} if the descriptor a primitive array 545 */ 546 public static boolean isPrimitiveArray(String descriptor) { 547 boolean primitive = true; 548 for (int i = 0, max = descriptor.length(); i < max; i++) { 549 char ch = descriptor.charAt(i); 550 if (ch == '[') { 551 continue; 552 } 553 primitive = (ch != 'L'); 554 break; 555 } 556 return primitive; 557 } 558 559 /** 560 * Determine whether boxing/unboxing can get from one type to the other. 561 * Assumes at least one of the types is in boxed form (i.e. single char descriptor). 562 * @return {@code true} if it is possible to get (via boxing) from one descriptor to the other 563 */ 564 public static boolean areBoxingCompatible(String desc1, String desc2) { 565 if (desc1.equals(desc2)) { 566 return true; 567 } 568 if (desc1.length() == 1) { 569 if (desc1.equals("Z")) { 570 return desc2.equals("Ljava/lang/Boolean"); 571 } 572 else if (desc1.equals("D")) { 573 return desc2.equals("Ljava/lang/Double"); 574 } 575 else if (desc1.equals("F")) { 576 return desc2.equals("Ljava/lang/Float"); 577 } 578 else if (desc1.equals("I")) { 579 return desc2.equals("Ljava/lang/Integer"); 580 } 581 else if (desc1.equals("J")) { 582 return desc2.equals("Ljava/lang/Long"); 583 } 584 } 585 else if (desc2.length() == 1) { 586 if (desc2.equals("Z")) { 587 return desc1.equals("Ljava/lang/Boolean"); 588 } 589 else if (desc2.equals("D")) { 590 return desc1.equals("Ljava/lang/Double"); 591 } 592 else if (desc2.equals("F")) { 593 return desc1.equals("Ljava/lang/Float"); 594 } 595 else if (desc2.equals("I")) { 596 return desc1.equals("Ljava/lang/Integer"); 597 } 598 else if (desc2.equals("J")) { 599 return desc1.equals("Ljava/lang/Long"); 600 } 601 } 602 return false; 603 } 604 605 /** 606 * Determine if the supplied descriptor is for a supported number type or boolean. The 607 * compilation process only (currently) supports certain number types. These are 608 * double, float, long and int. 609 * @param descriptor the descriptor for a type 610 * @return {@code true} if the descriptor is for a supported numeric type or boolean 611 */ 612 public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(String descriptor) { 613 if (descriptor == null) { 614 return false; 615 } 616 if (isPrimitiveOrUnboxableSupportedNumber(descriptor)) { 617 return true; 618 } 619 return ("Z".equals(descriptor) || descriptor.equals("Ljava/lang/Boolean")); 620 } 621 622 /** 623 * Determine if the supplied descriptor is for a supported number. The compilation 624 * process only (currently) supports certain number types. These are double, float, 625 * long and int. 626 * @param descriptor the descriptor for a type 627 * @return {@code true} if the descriptor is for a supported numeric type 628 */ 629 public static boolean isPrimitiveOrUnboxableSupportedNumber(String descriptor) { 630 if (descriptor == null) { 631 return false; 632 } 633 if (descriptor.length() == 1) { 634 return "DFIJ".contains(descriptor); 635 } 636 if (descriptor.startsWith("Ljava/lang/")) { 637 String name = descriptor.substring("Ljava/lang/".length()); 638 if (name.equals("Double") || name.equals("Float") || name.equals("Integer") || name.equals("Long")) { 639 return true; 640 } 641 } 642 return false; 643 } 644 645 /** 646 * Determine whether the given number is to be considered as an integer 647 * for the purposes of a numeric operation at the bytecode level. 648 * @param number the number to check 649 * @return {@code true} if it is an {@link Integer}, {@link Short} or {@link Byte} 650 */ 651 public static boolean isIntegerForNumericOp(Number number) { 652 return (number instanceof Integer || number instanceof Short || number instanceof Byte); 653 } 654 655 /** 656 * Convert a type descriptor to the single character primitive descriptor. 657 * @param descriptor a descriptor for a type that should have a primitive representation 658 * @return the single character descriptor for a primitive input descriptor 659 */ 660 public static char toPrimitiveTargetDesc(String descriptor) { 661 if (descriptor.length() == 1) { 662 return descriptor.charAt(0); 663 } 664 else if (descriptor.equals("Ljava/lang/Boolean")) { 665 return 'Z'; 666 } 667 else if (descriptor.equals("Ljava/lang/Byte")) { 668 return 'B'; 669 } 670 else if (descriptor.equals("Ljava/lang/Character")) { 671 return 'C'; 672 } 673 else if (descriptor.equals("Ljava/lang/Double")) { 674 return 'D'; 675 } 676 else if (descriptor.equals("Ljava/lang/Float")) { 677 return 'F'; 678 } 679 else if (descriptor.equals("Ljava/lang/Integer")) { 680 return 'I'; 681 } 682 else if (descriptor.equals("Ljava/lang/Long")) { 683 return 'J'; 684 } 685 else if (descriptor.equals("Ljava/lang/Short")) { 686 return 'S'; 687 } 688 else { 689 throw new IllegalStateException("No primitive for '" + descriptor + "'"); 690 } 691 } 692 693 /** 694 * Insert the appropriate CHECKCAST instruction for the supplied descriptor. 695 * @param mv the target visitor into which the instruction should be inserted 696 * @param descriptor the descriptor of the type to cast to 697 */ 698 public static void insertCheckCast(MethodVisitor mv, String descriptor) { 699 if (descriptor.length() != 1) { 700 if (descriptor.charAt(0) == '[') { 701 if (isPrimitiveArray(descriptor)) { 702 mv.visitTypeInsn(CHECKCAST, descriptor); 703 } 704 else { 705 mv.visitTypeInsn(CHECKCAST, descriptor + ";"); 706 } 707 } 708 else { 709 if (!descriptor.equals("Ljava/lang/Object")) { 710 // This is chopping off the 'L' to leave us with "java/lang/String" 711 mv.visitTypeInsn(CHECKCAST, descriptor.substring(1)); 712 } 713 } 714 } 715 } 716 717 /** 718 * Determine the appropriate boxing instruction for a specific type (if it needs 719 * boxing) and insert the instruction into the supplied visitor. 720 * @param mv the target visitor for the new instructions 721 * @param descriptor the descriptor of a type that may or may not need boxing 722 */ 723 public static void insertBoxIfNecessary(MethodVisitor mv, String descriptor) { 724 if (descriptor.length() == 1) { 725 insertBoxIfNecessary(mv, descriptor.charAt(0)); 726 } 727 } 728 729 /** 730 * Determine the appropriate boxing instruction for a specific type (if it needs 731 * boxing) and insert the instruction into the supplied visitor. 732 * @param mv the target visitor for the new instructions 733 * @param ch the descriptor of the type that might need boxing 734 */ 735 public static void insertBoxIfNecessary(MethodVisitor mv, char ch) { 736 switch (ch) { 737 case 'Z': 738 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); 739 break; 740 case 'B': 741 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); 742 break; 743 case 'C': 744 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false); 745 break; 746 case 'D': 747 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); 748 break; 749 case 'F': 750 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); 751 break; 752 case 'I': 753 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); 754 break; 755 case 'J': 756 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); 757 break; 758 case 'S': 759 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false); 760 break; 761 case 'L': 762 case 'V': 763 case '[': 764 // no box needed 765 break; 766 default: 767 throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'"); 768 } 769 } 770 771 /** 772 * Deduce the descriptor for a type. Descriptors are like JVM type names but missing the 773 * trailing ';' so for Object the descriptor is "Ljava/lang/Object" for int it is "I". 774 * @param type the type (may be primitive) for which to determine the descriptor 775 * @return the descriptor 776 */ 777 public static String toDescriptor(Class<?> type) { 778 String name = type.getName(); 779 if (type.isPrimitive()) { 780 switch (name.length()) { 781 case 3: 782 return "I"; 783 case 4: 784 if (name.equals("byte")) { 785 return "B"; 786 } 787 else if (name.equals("char")) { 788 return "C"; 789 } 790 else if (name.equals("long")) { 791 return "J"; 792 } 793 else if (name.equals("void")) { 794 return "V"; 795 } 796 break; 797 case 5: 798 if (name.equals("float")) { 799 return "F"; 800 } 801 else if (name.equals("short")) { 802 return "S"; 803 } 804 break; 805 case 6: 806 if (name.equals("double")) { 807 return "D"; 808 } 809 break; 810 case 7: 811 if (name.equals("boolean")) { 812 return "Z"; 813 } 814 break; 815 } 816 } 817 else { 818 if (name.charAt(0) != '[') { 819 return "L" + type.getName().replace('.', '/'); 820 } 821 else { 822 if (name.endsWith(";")) { 823 return name.substring(0, name.length() - 1).replace('.', '/'); 824 } 825 else { 826 return name; // array has primitive component type 827 } 828 } 829 } 830 return null; 831 } 832 833 /** 834 * Create an array of descriptors representing the parameter types for the supplied 835 * method. Returns a zero sized array if there are no parameters. 836 * @param method a Method 837 * @return a String array of descriptors, one entry for each method parameter 838 */ 839 public static String[] toParamDescriptors(Method method) { 840 return toDescriptors(method.getParameterTypes()); 841 } 842 843 /** 844 * Create an array of descriptors representing the parameter types for the supplied 845 * constructor. Returns a zero sized array if there are no parameters. 846 * @param ctor a Constructor 847 * @return a String array of descriptors, one entry for each constructor parameter 848 */ 849 public static String[] toParamDescriptors(Constructor<?> ctor) { 850 return toDescriptors(ctor.getParameterTypes()); 851 } 852 853 /** 854 * Create an array of descriptors from an array of classes. 855 * @param types the input array of classes 856 * @return an array of descriptors 857 */ 858 public static String[] toDescriptors(Class<?>[] types) { 859 int typesCount = types.length; 860 String[] descriptors = new String[typesCount]; 861 for (int p = 0; p < typesCount; p++) { 862 descriptors[p] = toDescriptor(types[p]); 863 } 864 return descriptors; 865 } 866 867 /** 868 * Create the optimal instruction for loading a number on the stack. 869 * @param mv where to insert the bytecode 870 * @param value the value to be loaded 871 */ 872 public static void insertOptimalLoad(MethodVisitor mv, int value) { 873 if (value < 6) { 874 mv.visitInsn(ICONST_0+value); 875 } 876 else if (value < Byte.MAX_VALUE) { 877 mv.visitIntInsn(BIPUSH, value); 878 } 879 else if (value < Short.MAX_VALUE) { 880 mv.visitIntInsn(SIPUSH, value); 881 } 882 else { 883 mv.visitLdcInsn(value); 884 } 885 } 886 887 /** 888 * Produce appropriate bytecode to store a stack item in an array. The 889 * instruction to use varies depending on whether the type 890 * is a primitive or reference type. 891 * @param mv where to insert the bytecode 892 * @param arrayElementType the type of the array elements 893 */ 894 public static void insertArrayStore(MethodVisitor mv, String arrayElementType) { 895 if (arrayElementType.length()==1) { 896 switch (arrayElementType.charAt(0)) { 897 case 'I': 898 mv.visitInsn(IASTORE); 899 break; 900 case 'J': 901 mv.visitInsn(LASTORE); 902 break; 903 case 'F': 904 mv.visitInsn(FASTORE); 905 break; 906 case 'D': 907 mv.visitInsn(DASTORE); 908 break; 909 case 'B': 910 mv.visitInsn(BASTORE); 911 break; 912 case 'C': 913 mv.visitInsn(CASTORE); 914 break; 915 case 'S': 916 mv.visitInsn(SASTORE); 917 break; 918 case 'Z': 919 mv.visitInsn(BASTORE); 920 break; 921 default: 922 throw new IllegalArgumentException( 923 "Unexpected arraytype " + arrayElementType.charAt(0)); 924 } 925 } 926 else { 927 mv.visitInsn(AASTORE); 928 } 929 } 930 931 /** 932 * Determine the appropriate T tag to use for the NEWARRAY bytecode. 933 * @param arraytype the array primitive component type 934 * @return the T tag to use for NEWARRAY 935 */ 936 public static int arrayCodeFor(String arraytype) { 937 switch (arraytype.charAt(0)) { 938 case 'I': return T_INT; 939 case 'J': return T_LONG; 940 case 'F': return T_FLOAT; 941 case 'D': return T_DOUBLE; 942 case 'B': return T_BYTE; 943 case 'C': return T_CHAR; 944 case 'S': return T_SHORT; 945 case 'Z': return T_BOOLEAN; 946 default: 947 throw new IllegalArgumentException("Unexpected arraytype " + arraytype.charAt(0)); 948 } 949 } 950 951 /** 952 * Return if the supplied array type has a core component reference type. 953 */ 954 public static boolean isReferenceTypeArray(String arraytype) { 955 int length = arraytype.length(); 956 for (int i = 0; i < length; i++) { 957 char ch = arraytype.charAt(i); 958 if (ch == '[') { 959 continue; 960 } 961 return (ch == 'L'); 962 } 963 return false; 964 } 965 966 /** 967 * Produce the correct bytecode to build an array. The opcode to use and the 968 * signature to pass along with the opcode can vary depending on the signature 969 * of the array type. 970 * @param mv the methodvisitor into which code should be inserted 971 * @param size the size of the array 972 * @param arraytype the type of the array 973 */ 974 public static void insertNewArrayCode(MethodVisitor mv, int size, String arraytype) { 975 insertOptimalLoad(mv, size); 976 if (arraytype.length() == 1) { 977 mv.visitIntInsn(NEWARRAY, CodeFlow.arrayCodeFor(arraytype)); 978 } 979 else { 980 if (arraytype.charAt(0) == '[') { 981 // Handling the nested array case here. 982 // If vararg is [[I then we want [I and not [I; 983 if (CodeFlow.isReferenceTypeArray(arraytype)) { 984 mv.visitTypeInsn(ANEWARRAY, arraytype + ";"); 985 } 986 else { 987 mv.visitTypeInsn(ANEWARRAY, arraytype); 988 } 989 } 990 else { 991 mv.visitTypeInsn(ANEWARRAY, arraytype.substring(1)); 992 } 993 } 994 } 995 996 /** 997 * For use in mathematical operators, handles converting from a (possibly boxed) 998 * number on the stack to a primitive numeric type. 999 * <p>For example, from a Integer to a double, just need to call 'Number.doubleValue()' 1000 * but from an int to a double, need to use the bytecode 'i2d'. 1001 * @param mv the method visitor when instructions should be appended 1002 * @param stackDescriptor a descriptor of the operand on the stack 1003 * @param targetDescriptor a primitive type descriptor 1004 */ 1005 public static void insertNumericUnboxOrPrimitiveTypeCoercion( 1006 MethodVisitor mv, String stackDescriptor, char targetDescriptor) { 1007 1008 if (!CodeFlow.isPrimitive(stackDescriptor)) { 1009 CodeFlow.insertUnboxNumberInsns(mv, targetDescriptor, stackDescriptor); 1010 } 1011 else { 1012 CodeFlow.insertAnyNecessaryTypeConversionBytecodes(mv, targetDescriptor, stackDescriptor); 1013 } 1014 } 1015 1016 public static String toBoxedDescriptor(String primitiveDescriptor) { 1017 switch (primitiveDescriptor.charAt(0)) { 1018 case 'I': return "Ljava/lang/Integer"; 1019 case 'J': return "Ljava/lang/Long"; 1020 case 'F': return "Ljava/lang/Float"; 1021 case 'D': return "Ljava/lang/Double"; 1022 case 'B': return "Ljava/lang/Byte"; 1023 case 'C': return "Ljava/lang/Character"; 1024 case 'S': return "Ljava/lang/Short"; 1025 case 'Z': return "Ljava/lang/Boolean"; 1026 default: 1027 throw new IllegalArgumentException("Unexpected non primitive descriptor " + primitiveDescriptor); 1028 } 1029 } 1030 1031 1032 /** 1033 * Interface used to generate fields. 1034 */ 1035 @FunctionalInterface 1036 public interface FieldAdder { 1037 1038 void generateField(ClassWriter cw, CodeFlow codeflow); 1039 } 1040 1041 1042 /** 1043 * Interface used to generate {@code clinit} static initializer blocks. 1044 */ 1045 @FunctionalInterface 1046 public interface ClinitAdder { 1047 1048 void generateCode(MethodVisitor mv, CodeFlow codeflow); 1049 } 1050 1051}