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