001 /* 002 $Id: BytecodeHelper.java 4287 2006-12-01 13:00:13Z blackdrag $ 003 004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 006 Redistribution and use of this software and associated documentation 007 ("Software"), with or without modification, are permitted provided 008 that the following conditions are met: 009 010 1. Redistributions of source code must retain copyright 011 statements and notices. Redistributions must also contain a 012 copy of this document. 013 014 2. Redistributions in binary form must reproduce the 015 above copyright notice, this list of conditions and the 016 following disclaimer in the documentation and/or other 017 materials provided with the distribution. 018 019 3. The name "groovy" must not be used to endorse or promote 020 products derived from this Software without prior written 021 permission of The Codehaus. For written permission, 022 please contact info@codehaus.org. 023 024 4. Products derived from this Software may not be called "groovy" 025 nor may "groovy" appear in their names without prior written 026 permission of The Codehaus. "groovy" is a registered 027 trademark of The Codehaus. 028 029 5. Due credit should be given to The Codehaus - 030 http://groovy.codehaus.org/ 031 032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 043 OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 */ 046 package org.codehaus.groovy.classgen; 047 048 import java.math.BigDecimal; 049 import java.math.BigInteger; 050 051 import org.codehaus.groovy.ast.ClassHelper; 052 import org.codehaus.groovy.ast.ClassNode; 053 import org.codehaus.groovy.ast.FieldNode; 054 import org.codehaus.groovy.ast.Parameter; 055 import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; 056 import org.objectweb.asm.MethodVisitor; 057 import org.objectweb.asm.Opcodes; 058 import org.objectweb.asm.Label; 059 060 /** 061 * A helper class for bytecode generation with AsmClassGenerator. 062 * 063 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> 064 * @author <a href="mailto:b55r@sina.com">Bing Ran</a> 065 * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a> 066 * @version $Revision: 4287 $ 067 */ 068 public class BytecodeHelper implements Opcodes { 069 070 private MethodVisitor cv; 071 072 public MethodVisitor getMethodVisitor() { 073 return cv; 074 } 075 076 public BytecodeHelper(MethodVisitor cv) { 077 this.cv = cv; 078 } 079 080 /** 081 * box the primitive value on the stack 082 * @param type 083 */ 084 public void quickBoxIfNecessary(ClassNode type) { 085 String descr = getTypeDescription(type); 086 if (type == ClassHelper.boolean_TYPE) { 087 boxBoolean(); 088 } 089 else if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { 090 ClassNode wrapper = ClassHelper.getWrapper(type); 091 String internName = getClassInternalName(wrapper); 092 cv.visitTypeInsn(NEW, internName); 093 cv.visitInsn(DUP); 094 if (type==ClassHelper.double_TYPE || type==ClassHelper.long_TYPE) { 095 cv.visitInsn(DUP2_X2); 096 cv.visitInsn(POP2); 097 } else { 098 cv.visitInsn(DUP2_X1); 099 cv.visitInsn(POP2); 100 } 101 cv.visitMethodInsn(INVOKESPECIAL, internName, "<init>", "(" + descr + ")V"); 102 } 103 } 104 105 public void quickUnboxIfNecessary(ClassNode type){ 106 if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { // todo care when BigDecimal or BigIneteger on stack 107 ClassNode wrapper = ClassHelper.getWrapper(type); 108 String internName = getClassInternalName(wrapper); 109 if (type == ClassHelper.boolean_TYPE) { 110 cv.visitTypeInsn(CHECKCAST, internName); 111 cv.visitMethodInsn(INVOKEVIRTUAL, internName, type.getName() + "Value", "()" + getTypeDescription(type)); 112 } else { // numbers 113 cv.visitTypeInsn(CHECKCAST, "java/lang/Number"); 114 cv.visitMethodInsn(INVOKEVIRTUAL, /*internName*/"java/lang/Number", type.getName() + "Value", "()" + getTypeDescription(type)); 115 } 116 } 117 } 118 119 /** 120 * Generates the bytecode to autobox the current value on the stack 121 */ 122 public void box(Class type) { 123 if (type.isPrimitive() && type != void.class) { 124 String returnString = "(" + getTypeDescription(type) + ")Ljava/lang/Object;"; 125 cv.visitMethodInsn(INVOKESTATIC, getClassInternalName(DefaultTypeTransformation.class.getName()), "box", returnString); 126 } 127 } 128 129 public void box(ClassNode type) { 130 if (type.isPrimaryClassNode()) return; 131 box(type.getTypeClass()); 132 } 133 134 /** 135 * Generates the bytecode to unbox the current value on the stack 136 */ 137 public void unbox(Class type) { 138 if (type.isPrimitive() && type != Void.TYPE) { 139 String returnString = "(Ljava/lang/Object;)" + getTypeDescription(type); 140 cv.visitMethodInsn( 141 INVOKESTATIC, 142 getClassInternalName(DefaultTypeTransformation.class.getName()), 143 type.getName() + "Unbox", 144 returnString); 145 } 146 } 147 148 public void unbox(ClassNode type) { 149 if (type.isPrimaryClassNode()) return; 150 unbox(type.getTypeClass()); 151 } 152 153 public static String getClassInternalName(ClassNode t){ 154 if (t.isPrimaryClassNode()){ 155 return getClassInternalName(t.getName()); 156 } 157 return getClassInternalName(t.getTypeClass()); 158 } 159 160 public static String getClassInternalName(Class t) { 161 return org.objectweb.asm.Type.getInternalName(t); 162 } 163 164 /** 165 * @return the ASM internal name of the type 166 */ 167 public static String getClassInternalName(String name) { 168 return name.replace('.', '/'); 169 } 170 171 /** 172 * @return the ASM method type descriptor 173 */ 174 public static String getMethodDescriptor(ClassNode returnType, Parameter[] parameters) { 175 StringBuffer buffer = new StringBuffer("("); 176 for (int i = 0; i < parameters.length; i++) { 177 buffer.append(getTypeDescription(parameters[i].getType())); 178 } 179 buffer.append(")"); 180 buffer.append(getTypeDescription(returnType)); 181 return buffer.toString(); 182 } 183 184 /** 185 * @return the ASM method type descriptor 186 */ 187 public static String getMethodDescriptor(Class returnType, Class[] paramTypes) { 188 // lets avoid class loading 189 StringBuffer buffer = new StringBuffer("("); 190 for (int i = 0; i < paramTypes.length; i++) { 191 buffer.append(getTypeDescription(paramTypes[i])); 192 } 193 buffer.append(")"); 194 buffer.append(getTypeDescription(returnType)); 195 return buffer.toString(); 196 } 197 198 public static String getTypeDescription(Class c) { 199 return org.objectweb.asm.Type.getDescriptor(c); 200 } 201 202 /** 203 * array types are special: 204 * eg.: String[]: classname: [Ljava.lang.String; 205 * Object: classname: java.lang.Object 206 * int[] : classname: [I 207 * unlike getTypeDescription '.' is not replaces by '/'. 208 * it seems that makes problems for 209 * the class loading if '.' is replaced by '/' 210 * @return the ASM type description for class loading 211 */ 212 public static String getClassLoadingTypeDescription(ClassNode c) { 213 StringBuffer buf = new StringBuffer(); 214 boolean array = false; 215 while (true) { 216 if (c.isArray()) { 217 buf.append('['); 218 c = c.getComponentType(); 219 array = true; 220 } else { 221 if (ClassHelper.isPrimitiveType(c)) { 222 buf.append(getTypeDescription(c)); 223 } else { 224 if (array) buf.append('L'); 225 buf.append(c.getName()); 226 if(array) buf.append(';'); 227 } 228 return buf.toString(); 229 } 230 } 231 } 232 233 /** 234 * array types are special: 235 * eg.: String[]: classname: [Ljava/lang/String; 236 * int[]: [I 237 * @return the ASM type description 238 */ 239 public static String getTypeDescription(ClassNode c) { 240 StringBuffer buf = new StringBuffer(); 241 ClassNode d = c; 242 while (true) { 243 if (ClassHelper.isPrimitiveType(d)) { 244 char car; 245 if (d == ClassHelper.int_TYPE) { 246 car = 'I'; 247 } else if (d == ClassHelper.VOID_TYPE) { 248 car = 'V'; 249 } else if (d == ClassHelper.boolean_TYPE) { 250 car = 'Z'; 251 } else if (d == ClassHelper.byte_TYPE) { 252 car = 'B'; 253 } else if (d == ClassHelper.char_TYPE) { 254 car = 'C'; 255 } else if (d == ClassHelper.short_TYPE) { 256 car = 'S'; 257 } else if (d == ClassHelper.double_TYPE) { 258 car = 'D'; 259 } else if (d == ClassHelper.float_TYPE) { 260 car = 'F'; 261 } else /* long */{ 262 car = 'J'; 263 } 264 buf.append(car); 265 return buf.toString(); 266 } else if (d.isArray()) { 267 buf.append('['); 268 d = d.getComponentType(); 269 } else { 270 buf.append('L'); 271 String name = d.getName(); 272 int len = name.length(); 273 for (int i = 0; i < len; ++i) { 274 char car = name.charAt(i); 275 buf.append(car == '.' ? '/' : car); 276 } 277 buf.append(';'); 278 return buf.toString(); 279 } 280 } 281 } 282 283 /** 284 * @return an array of ASM internal names of the type 285 */ 286 public static String[] getClassInternalNames(ClassNode[] names) { 287 int size = names.length; 288 String[] answer = new String[size]; 289 for (int i = 0; i < size; i++) { 290 answer[i] = getClassInternalName(names[i]); 291 } 292 return answer; 293 } 294 295 protected void pushConstant(boolean value) { 296 if (value) { 297 cv.visitInsn(ICONST_1); 298 } 299 else { 300 cv.visitInsn(ICONST_0); 301 } 302 } 303 304 protected void pushConstant(int value) { 305 switch (value) { 306 case 0 : 307 cv.visitInsn(ICONST_0); 308 break; 309 case 1 : 310 cv.visitInsn(ICONST_1); 311 break; 312 case 2 : 313 cv.visitInsn(ICONST_2); 314 break; 315 case 3 : 316 cv.visitInsn(ICONST_3); 317 break; 318 case 4 : 319 cv.visitInsn(ICONST_4); 320 break; 321 case 5 : 322 cv.visitInsn(ICONST_5); 323 break; 324 default : 325 if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { 326 cv.visitIntInsn(BIPUSH, value); 327 } 328 else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { 329 cv.visitIntInsn(SIPUSH, value); 330 } 331 else { 332 cv.visitLdcInsn(new Integer(value)); 333 } 334 } 335 } 336 337 public void doCast(Class type) { 338 if (type!=Object.class) { 339 if (type.isPrimitive() && type!=Void.TYPE) { 340 unbox(type); 341 } 342 else { 343 cv.visitTypeInsn( 344 CHECKCAST, 345 type.isArray() ? getTypeDescription(type) : getClassInternalName(type.getName())); 346 } 347 } 348 } 349 350 public void doCast(ClassNode type) { 351 if (type==ClassHelper.OBJECT_TYPE) return; 352 if (ClassHelper.isPrimitiveType(type) && type!=ClassHelper.VOID_TYPE) { 353 unbox(type); 354 } 355 else { 356 cv.visitTypeInsn( 357 CHECKCAST, 358 type.isArray() ? getTypeDescription(type) : getClassInternalName(type)); 359 } 360 } 361 362 public void load(ClassNode type, int idx) { 363 if (type==ClassHelper.double_TYPE) { 364 cv.visitVarInsn(DLOAD, idx); 365 } 366 else if (type==ClassHelper.float_TYPE) { 367 cv.visitVarInsn(FLOAD, idx); 368 } 369 else if (type==ClassHelper.long_TYPE) { 370 cv.visitVarInsn(LLOAD, idx); 371 } 372 else if ( 373 type==ClassHelper.boolean_TYPE 374 || type==ClassHelper.char_TYPE 375 || type==ClassHelper.byte_TYPE 376 || type==ClassHelper.int_TYPE 377 || type==ClassHelper.short_TYPE) 378 { 379 cv.visitVarInsn(ILOAD, idx); 380 } 381 else { 382 cv.visitVarInsn(ALOAD, idx); 383 } 384 } 385 386 public void load(Variable v) { 387 load(v.getType(), v.getIndex()); 388 } 389 390 public void store(Variable v, boolean markStart) { 391 ClassNode type = v.getType(); 392 unbox(type); 393 int idx = v.getIndex(); 394 395 if (type==ClassHelper.double_TYPE) { 396 cv.visitVarInsn(DSTORE, idx); 397 } 398 else if (type==ClassHelper.float_TYPE) { 399 cv.visitVarInsn(FSTORE, idx); 400 } 401 else if (type==ClassHelper.long_TYPE) { 402 cv.visitVarInsn(LSTORE, idx); 403 } 404 else if ( 405 type==ClassHelper.boolean_TYPE 406 || type==ClassHelper.char_TYPE 407 || type==ClassHelper.byte_TYPE 408 || type==ClassHelper.int_TYPE 409 || type==ClassHelper.short_TYPE) { 410 cv.visitVarInsn(ISTORE, idx); 411 } 412 else { 413 cv.visitVarInsn(ASTORE, idx); 414 } 415 } 416 417 public void store(Variable v) { 418 store(v, false); 419 } 420 421 /** 422 * load the constant on the operand stack. primitives auto-boxed. 423 */ 424 void loadConstant (Object value) { 425 if (value == null) { 426 cv.visitInsn(ACONST_NULL); 427 } 428 else if (value instanceof String) { 429 cv.visitLdcInsn(value); 430 } 431 else if (value instanceof Character) { 432 String className = "java/lang/Character"; 433 cv.visitTypeInsn(NEW, className); 434 cv.visitInsn(DUP); 435 cv.visitLdcInsn(value); 436 String methodType = "(C)V"; 437 cv.visitMethodInsn(INVOKESPECIAL, className, "<init>", methodType); 438 } 439 else if (value instanceof Number) { 440 /** todo it would be more efficient to generate class constants */ 441 Number n = (Number) value; 442 String className = BytecodeHelper.getClassInternalName(value.getClass().getName()); 443 cv.visitTypeInsn(NEW, className); 444 cv.visitInsn(DUP); 445 String methodType; 446 if (n instanceof Integer) { 447 //pushConstant(n.intValue()); 448 cv.visitLdcInsn(n); 449 methodType = "(I)V"; 450 } 451 else if (n instanceof Double) { 452 cv.visitLdcInsn(n); 453 methodType = "(D)V"; 454 } 455 else if (n instanceof Float) { 456 cv.visitLdcInsn(n); 457 methodType = "(F)V"; 458 } 459 else if (n instanceof Long) { 460 cv.visitLdcInsn(n); 461 methodType = "(J)V"; 462 } 463 else if (n instanceof BigDecimal) { 464 cv.visitLdcInsn(n.toString()); 465 methodType = "(Ljava/lang/String;)V"; 466 } 467 else if (n instanceof BigInteger) { 468 cv.visitLdcInsn(n.toString()); 469 methodType = "(Ljava/lang/String;)V"; 470 } 471 else if (n instanceof Short) { 472 cv.visitLdcInsn(n); 473 methodType = "(S)V"; 474 } 475 else if (n instanceof Byte) { 476 cv.visitLdcInsn(n); 477 methodType = "(B)V"; 478 } 479 else { 480 throw new ClassGeneratorException( 481 "Cannot generate bytecode for constant: " + value 482 + " of type: " + value.getClass().getName() 483 + ". Numeric constant type not supported."); 484 } 485 cv.visitMethodInsn(INVOKESPECIAL, className, "<init>", methodType); 486 } 487 else if (value instanceof Boolean) { 488 Boolean bool = (Boolean) value; 489 String text = (bool.booleanValue()) ? "TRUE" : "FALSE"; 490 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", text, "Ljava/lang/Boolean;"); 491 } 492 else if (value instanceof Class) { 493 Class vc = (Class) value; 494 if (vc.getName().equals("java.lang.Void")) { 495 // load nothing here for void 496 } else { 497 throw new ClassGeneratorException( 498 "Cannot generate bytecode for constant: " + value + " of type: " + value.getClass().getName()); 499 } 500 } 501 else { 502 throw new ClassGeneratorException( 503 "Cannot generate bytecode for constant: " + value + " of type: " + value.getClass().getName()); 504 } 505 } 506 507 508 /** 509 * load the value of the variable on the operand stack. unbox it if it's a reference 510 * @param variable 511 */ 512 public void loadVar(Variable variable) { 513 int index = variable.getIndex(); 514 if (variable.isHolder()) { 515 cv.visitVarInsn(ALOAD, index); 516 cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;"); 517 } else { 518 load(variable); 519 if (variable!=Variable.THIS_VARIABLE && variable!=Variable.SUPER_VARIABLE) { 520 box(variable.getType()); 521 } 522 } 523 } 524 525 public void storeVar(Variable variable) { 526 String type = variable.getTypeName(); 527 int index = variable.getIndex(); 528 529 if (variable.isHolder()) { 530 cv.visitVarInsn(ALOAD, index); 531 cv.visitInsn(SWAP); 532 cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V"); 533 } 534 else { 535 store(variable,false); 536 } 537 } 538 539 public void putField(FieldNode fld) { 540 putField(fld, getClassInternalName(fld.getOwner())); 541 } 542 543 public void putField(FieldNode fld, String ownerName) { 544 cv.visitFieldInsn(PUTFIELD, ownerName, fld.getName(), getTypeDescription(fld.getType())); 545 } 546 547 public void swapObjectWith(ClassNode type) { 548 if (type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE) { 549 cv.visitInsn(DUP_X2); 550 cv.visitInsn(POP); 551 } else { 552 cv.visitInsn(SWAP); 553 } 554 } 555 556 public void swapWithObject(ClassNode type) { 557 if (type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE) { 558 cv.visitInsn(DUP2_X1); 559 cv.visitInsn(POP2); 560 } else { 561 cv.visitInsn(SWAP); 562 } 563 } 564 565 public static ClassNode boxOnPrimitive(ClassNode type) { 566 if (!type.isArray()) return ClassHelper.getWrapper(type); 567 return boxOnPrimitive(type.getComponentType()).makeArray(); 568 } 569 570 /** 571 * convert boolean to Boolean 572 */ 573 public void boxBoolean() { 574 Label l0 = new Label(); 575 cv.visitJumpInsn(IFEQ, l0); 576 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;"); 577 Label l1 = new Label(); 578 cv.visitJumpInsn(GOTO, l1); 579 cv.visitLabel(l0); 580 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;"); 581 cv.visitLabel(l1); 582 } 583 584 /** 585 * negate a boolean on stack. true->false, false->true 586 */ 587 public void negateBoolean(){ 588 // code to negate the primitive boolean 589 Label endLabel = new Label(); 590 Label falseLabel = new Label(); 591 cv.visitJumpInsn(IFNE,falseLabel); 592 cv.visitInsn(ICONST_1); 593 cv.visitJumpInsn(GOTO,endLabel); 594 cv.visitLabel(falseLabel); 595 cv.visitInsn(ICONST_0); 596 cv.visitLabel(endLabel); 597 } 598 599 /** 600 * load a message on the stack and remove it right away. Good for put a mark in the generated bytecode for debugging purpose. 601 * @param msg 602 */ 603 public void mark(String msg) { 604 cv.visitLdcInsn(msg); 605 cv.visitInsn(POP); 606 } 607 608 /** 609 * returns a name that Class.forName() can take. Notablely for arrays: 610 * [I, [Ljava.lang.String; etc 611 * Regular object type: java.lang.String 612 * @param name 613 */ 614 public static String formatNameForClassLoading(String name) { 615 if (name.equals("int") 616 || name.equals("long") 617 || name.equals("short") 618 || name.equals("float") 619 || name.equals("double") 620 || name.equals("byte") 621 || name.equals("char") 622 || name.equals("boolean") 623 || name.equals("void") 624 ) { 625 return name; 626 } 627 628 if (name == null) { 629 return "java.lang.Object;"; 630 } 631 632 if (name.startsWith("[")) { 633 return name.replace('/', '.'); 634 } 635 636 if (name.startsWith("L")) { 637 name = name.substring(1); 638 if (name.endsWith(";")) { 639 name = name.substring(0, name.length() - 1); 640 } 641 return name.replace('/', '.'); 642 } 643 644 String prefix = ""; 645 if (name.endsWith("[]")) { // todo need process multi 646 prefix = "["; 647 name = name.substring(0, name.length() - 2); 648 if (name.equals("int")) { 649 return prefix + "I"; 650 } 651 else if (name.equals("long")) { 652 return prefix + "J"; 653 } 654 else if (name.equals("short")) { 655 return prefix + "S"; 656 } 657 else if (name.equals("float")) { 658 return prefix + "F"; 659 } 660 else if (name.equals("double")) { 661 return prefix + "D"; 662 } 663 else if (name.equals("byte")) { 664 return prefix + "B"; 665 } 666 else if (name.equals("char")) { 667 return prefix + "C"; 668 } 669 else if (name.equals("boolean")) { 670 return prefix + "Z"; 671 } 672 else { 673 return prefix + "L" + name.replace('/', '.') + ";"; 674 } 675 } 676 return name.replace('/', '.'); 677 678 } 679 680 public void dup() { 681 cv.visitInsn(DUP); 682 } 683 684 public void doReturn(ClassNode returnType) { 685 if (returnType==ClassHelper.double_TYPE) { 686 cv.visitInsn(DRETURN); 687 } else if (returnType==ClassHelper.float_TYPE) { 688 cv.visitInsn(FRETURN); 689 } else if (returnType==ClassHelper.long_TYPE) { 690 cv.visitInsn(LRETURN); 691 } else if ( 692 returnType==ClassHelper.boolean_TYPE 693 || returnType==ClassHelper.char_TYPE 694 || returnType==ClassHelper.byte_TYPE 695 || returnType==ClassHelper.int_TYPE 696 || returnType==ClassHelper.short_TYPE) 697 { 698 //byte,short,boolean,int are all IRETURN 699 cv.visitInsn(IRETURN); 700 } else if (returnType==ClassHelper.VOID_TYPE){ 701 cv.visitInsn(RETURN); 702 } else { 703 cv.visitInsn(ARETURN); 704 } 705 706 } 707 708 }