001 /* 002 $Id: BytecodeHelper.java,v 1.22 2005/11/13 16:42:11 blackdrag Exp $ 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.ScriptBytecodeAdapter; 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 * @version $Revision: 1.22 $ 066 */ 067 public class BytecodeHelper implements Opcodes { 068 069 private MethodVisitor cv; 070 071 public MethodVisitor getMethodVisitor() { 072 return cv; 073 } 074 075 public BytecodeHelper(MethodVisitor cv) { 076 this.cv = cv; 077 } 078 079 /** 080 * box the primitive value on the stack 081 * @param cls 082 */ 083 public void quickBoxIfNecessary(ClassNode type) { 084 String descr = getTypeDescription(type); 085 if (type == ClassHelper.boolean_TYPE) { 086 boxBoolean(); 087 } 088 else if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { 089 // use a special integer pool in the invokerhelper 090 if (type == ClassHelper.int_TYPE) { 091 cv.visitMethodInsn( 092 INVOKESTATIC, 093 getClassInternalName(ScriptBytecodeAdapter.class.getName()), 094 "integerValue", 095 "(I)Ljava/lang/Integer;" 096 ); 097 return; 098 } 099 100 ClassNode wrapper = ClassHelper.getWrapper(type); 101 String internName = getClassInternalName(wrapper); 102 cv.visitTypeInsn(NEW, internName); 103 cv.visitInsn(DUP); 104 if (type==ClassHelper.double_TYPE || type==ClassHelper.long_TYPE) { 105 cv.visitInsn(DUP2_X2); 106 cv.visitInsn(POP2); 107 } else { 108 cv.visitInsn(DUP2_X1); 109 cv.visitInsn(POP2); 110 } 111 cv.visitMethodInsn(INVOKESPECIAL, internName, "<init>", "(" + descr + ")V"); 112 113 // Operand opr = new Operand(ITEM_Object, wrapperName, "", ""); 114 // _safePop(); 115 // push(opr); 116 } 117 } 118 public void quickUnboxIfNecessary(ClassNode type){ 119 if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { // todo care when BigDecimal or BigIneteger on stack 120 ClassNode wrapper = ClassHelper.getWrapper(type); 121 String internName = getClassInternalName(wrapper); 122 if (type == ClassHelper.boolean_TYPE) { 123 cv.visitTypeInsn(CHECKCAST, internName); 124 cv.visitMethodInsn(INVOKEVIRTUAL, internName, type.getName() + "Value", "()" + getTypeDescription(type)); 125 } else { // numbers 126 cv.visitTypeInsn(CHECKCAST, "java/lang/Number"); 127 cv.visitMethodInsn(INVOKEVIRTUAL, /*internName*/"java/lang/Number", type.getName() + "Value", "()" + getTypeDescription(type)); 128 } 129 } 130 } 131 132 /** 133 * Generates the bytecode to autobox the current value on the stack 134 */ 135 public void box(Class type) { 136 if (type.isPrimitive() && type != void.class) { 137 String returnString = "(" + getTypeDescription(type.getName()) + ")Ljava/lang/Object;"; 138 cv.visitMethodInsn(INVOKESTATIC, getClassInternalName(ScriptBytecodeAdapter.class.getName()), "box", returnString); 139 } 140 } 141 142 public void box(ClassNode type) { 143 if (type.isPrimaryClassNode()) return; 144 box(type.getTypeClass()); 145 } 146 147 /** 148 * Generates the bytecode to unbox the current value on the stack 149 */ 150 public void unbox(Class type) { 151 if (type.isPrimitive() && type != Void.TYPE) { 152 String returnString = "(Ljava/lang/Object;)" + getTypeDescription(type.getName()); 153 cv.visitMethodInsn( 154 INVOKESTATIC, 155 getClassInternalName(ScriptBytecodeAdapter.class.getName()), 156 type.getName() + "Unbox", 157 returnString); 158 } 159 } 160 161 public void unbox(ClassNode type) { 162 if (type.isPrimaryClassNode()) return; 163 unbox(type.getTypeClass()); 164 } 165 166 /** 167 * array types are special: 168 * eg.: String[]: classname: [Ljava/lang/String; 169 * int[]: [I 170 * @return the ASM type description 171 */ 172 public static String getTypeDescription(String name) { 173 // lets avoid class loading 174 // return getType(name).getDescriptor(); 175 if (name == null) { 176 return "Ljava/lang/Object;"; 177 } 178 if (name.equals("void")) { 179 return "V"; 180 } 181 182 if (name.startsWith("[")) { // todo need to take care of multi-dimentional array 183 return name.replace('.', '/'); 184 } 185 186 String prefix = ""; 187 if (name.endsWith("[]")) { 188 prefix = "["; 189 name = name.substring(0, name.length() - 2); 190 } 191 192 if (name.equals("int")) { 193 return prefix + "I"; 194 } 195 else if (name.equals("long")) { 196 return prefix + "J"; 197 } 198 else if (name.equals("short")) { 199 return prefix + "S"; 200 } 201 else if (name.equals("float")) { 202 return prefix + "F"; 203 } 204 else if (name.equals("double")) { 205 return prefix + "D"; 206 } 207 else if (name.equals("byte")) { 208 return prefix + "B"; 209 } 210 else if (name.equals("char")) { 211 return prefix + "C"; 212 } 213 else if (name.equals("boolean")) { 214 return prefix + "Z"; 215 } 216 return prefix + "L" + name.replace('.', '/') + ";"; 217 } 218 219 public static String getClassInternalName(ClassNode t){ 220 if (t.isPrimaryClassNode()){ 221 return getClassInternalName(t.getName()); 222 } 223 return getClassInternalName(t.getTypeClass()); 224 } 225 226 public static String getClassInternalName(Class t) { 227 return org.objectweb.asm.Type.getInternalName(t); 228 } 229 230 /** 231 * @return the ASM internal name of the type 232 */ 233 public static String getClassInternalName(String name) { 234 String answer = name.replace('.', '/'); 235 while (answer.endsWith("[]")) { 236 answer = "[" + answer.substring(0, answer.length() - 2); 237 } 238 return answer; 239 } 240 241 /** 242 * @return the ASM method type descriptor 243 */ 244 public static String getMethodDescriptor(ClassNode returnType, Parameter[] parameters) { 245 StringBuffer buffer = new StringBuffer("("); 246 for (int i = 0; i < parameters.length; i++) { 247 buffer.append(getTypeDescription(parameters[i].getType().getName())); 248 } 249 buffer.append(")"); 250 buffer.append(getTypeDescription(returnType.getName())); 251 return buffer.toString(); 252 } 253 254 /** 255 * @return the ASM method type descriptor 256 */ 257 public static String getMethodDescriptor(Class returnType, Class[] paramTypes) { 258 // lets avoid class loading 259 StringBuffer buffer = new StringBuffer("("); 260 for (int i = 0; i < paramTypes.length; i++) { 261 buffer.append(getTypeDescription(paramTypes[i].getName())); 262 } 263 buffer.append(")"); 264 buffer.append(getTypeDescription(returnType.getName())); 265 return buffer.toString(); 266 } 267 268 269 270 public static String getTypeDescription(ClassNode type) { 271 if (type.isArray()) { 272 return type.getName().replace('.', '/'); 273 } 274 else { 275 return getTypeDescription(type.getName()); 276 } 277 } 278 279 /** 280 * @return an array of ASM internal names of the type 281 */ 282 public static String[] getClassInternalNames(ClassNode[] names) { 283 int size = names.length; 284 String[] answer = new String[size]; 285 for (int i = 0; i < size; i++) { 286 answer[i] = getClassInternalName(names[i]); 287 } 288 return answer; 289 } 290 291 protected void pushConstant(boolean value) { 292 if (value) { 293 cv.visitInsn(ICONST_1); 294 } 295 else { 296 cv.visitInsn(ICONST_0); 297 } 298 } 299 300 protected void pushConstant(int value) { 301 switch (value) { 302 case 0 : 303 cv.visitInsn(ICONST_0); 304 break; 305 case 1 : 306 cv.visitInsn(ICONST_1); 307 break; 308 case 2 : 309 cv.visitInsn(ICONST_2); 310 break; 311 case 3 : 312 cv.visitInsn(ICONST_3); 313 break; 314 case 4 : 315 cv.visitInsn(ICONST_4); 316 break; 317 case 5 : 318 cv.visitInsn(ICONST_5); 319 break; 320 default : 321 if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { 322 cv.visitIntInsn(BIPUSH, value); 323 } 324 else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { 325 cv.visitIntInsn(SIPUSH, value); 326 } 327 else { 328 cv.visitLdcInsn(new Integer(value)); 329 } 330 } 331 } 332 333 public void doCast(Class type) { 334 if (type!=Object.class) { 335 if (type.isPrimitive() && type!=Void.TYPE) { 336 unbox(type); 337 } 338 else { 339 cv.visitTypeInsn( 340 CHECKCAST, 341 type.isArray() ? getTypeDescription(type.getName()) : getClassInternalName(type.getName())); 342 } 343 } 344 } 345 346 public void doCast(ClassNode type) { 347 if (type==ClassHelper.OBJECT_TYPE) return; 348 if (ClassHelper.isPrimitiveType(type) && type!=ClassHelper.VOID_TYPE) { 349 unbox(type); 350 } 351 else { 352 cv.visitTypeInsn( 353 CHECKCAST, 354 type.isArray() ? getTypeDescription(type) : getClassInternalName(type)); 355 } 356 } 357 358 public void load(ClassNode type, int idx) { 359 if (type==ClassHelper.double_TYPE) { 360 cv.visitVarInsn(DLOAD, idx); 361 } 362 else if (type==ClassHelper.float_TYPE) { 363 cv.visitVarInsn(FLOAD, idx); 364 } 365 else if (type==ClassHelper.long_TYPE) { 366 cv.visitVarInsn(LLOAD, idx); 367 } 368 else if ( 369 type==ClassHelper.boolean_TYPE 370 || type==ClassHelper.char_TYPE 371 || type==ClassHelper.byte_TYPE 372 || type==ClassHelper.int_TYPE 373 || type==ClassHelper.short_TYPE) 374 { 375 cv.visitVarInsn(ILOAD, idx); 376 } 377 else { 378 cv.visitVarInsn(ALOAD, idx); 379 } 380 } 381 382 public void load(Variable v) { 383 load(v.getType(), v.getIndex()); 384 } 385 386 public void store(Variable v, boolean markStart) { 387 String type = v.getTypeName(); 388 int idx = v.getIndex(); 389 390 if (type.equals("double")) { 391 cv.visitVarInsn(DSTORE, idx); 392 } 393 else if (type.equals("float")) { 394 cv.visitVarInsn(FSTORE, idx); 395 } 396 else if (type.equals("long")) { 397 cv.visitVarInsn(LSTORE, idx); 398 } 399 else if ( 400 type.equals("boolean") 401 || type.equals("char") 402 || type.equals("byte") 403 || type.equals("int") 404 || type.equals("short")) { 405 cv.visitVarInsn(ISTORE, idx); 406 } 407 else { 408 cv.visitVarInsn(ASTORE, idx); 409 } 410 if (AsmClassGenerator.CREATE_DEBUG_INFO && markStart) { 411 Label l = v.getStartLabel(); 412 if (l != null) { 413 cv.visitLabel(l); 414 } else { 415 System.out.println("start label == null! what to do about this?"); 416 } 417 } 418 } 419 420 public void store(Variable v) { 421 store(v, false); 422 } 423 424 /** 425 * load the constant on the operand stack. primitives auto-boxed. 426 */ 427 void loadConstant (Object value) { 428 if (value == null) { 429 cv.visitInsn(ACONST_NULL); 430 } 431 else if (value instanceof String) { 432 cv.visitLdcInsn(value); 433 } 434 else if (value instanceof Character) { 435 String className = "java/lang/Character"; 436 cv.visitTypeInsn(NEW, className); 437 cv.visitInsn(DUP); 438 cv.visitLdcInsn(value); 439 String methodType = "(C)V"; 440 cv.visitMethodInsn(INVOKESPECIAL, className, "<init>", methodType); 441 } 442 else if (value instanceof Number) { 443 /** todo it would be more efficient to generate class constants */ 444 Number n = (Number) value; 445 String className = BytecodeHelper.getClassInternalName(value.getClass().getName()); 446 cv.visitTypeInsn(NEW, className); 447 cv.visitInsn(DUP); 448 String methodType; 449 if (n instanceof Integer) { 450 //pushConstant(n.intValue()); 451 cv.visitLdcInsn(n); 452 methodType = "(I)V"; 453 } 454 else if (n instanceof Double) { 455 cv.visitLdcInsn(n); 456 methodType = "(D)V"; 457 } 458 else if (n instanceof Float) { 459 cv.visitLdcInsn(n); 460 methodType = "(F)V"; 461 } 462 else if (n instanceof Long) { 463 cv.visitLdcInsn(n); 464 methodType = "(J)V"; 465 } 466 else if (n instanceof BigDecimal) { 467 cv.visitLdcInsn(n.toString()); 468 methodType = "(Ljava/lang/String;)V"; 469 } 470 else if (n instanceof BigInteger) { 471 cv.visitLdcInsn(n.toString()); 472 methodType = "(Ljava/lang/String;)V"; 473 } 474 else if (n instanceof Short) { 475 cv.visitLdcInsn(n); 476 methodType = "(S)V"; 477 } 478 else if (n instanceof Byte) { 479 cv.visitLdcInsn(n); 480 methodType = "(B)V"; 481 } 482 else { 483 throw new ClassGeneratorException( 484 "Cannot generate bytecode for constant: " + value 485 + " of type: " + value.getClass().getName() 486 + ". Numeric constant type not supported."); 487 } 488 cv.visitMethodInsn(INVOKESPECIAL, className, "<init>", methodType); 489 } 490 else if (value instanceof Boolean) { 491 Boolean bool = (Boolean) value; 492 String text = (bool.booleanValue()) ? "TRUE" : "FALSE"; 493 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", text, "Ljava/lang/Boolean;"); 494 } 495 else if (value instanceof Class) { 496 Class vc = (Class) value; 497 if (vc.getName().equals("java.lang.Void")) { 498 // load nothing here for void 499 } else { 500 throw new ClassGeneratorException( 501 "Cannot generate bytecode for constant: " + value + " of type: " + value.getClass().getName()); 502 } 503 } 504 else { 505 throw new ClassGeneratorException( 506 "Cannot generate bytecode for constant: " + value + " of type: " + value.getClass().getName()); 507 } 508 } 509 510 511 /** 512 * load the value of the variable on the operand stack. unbox it if it's a reference 513 * @param variable 514 * @param holder 515 */ 516 public void loadVar(Variable variable, boolean holder) { 517 String type = variable.getTypeName(); 518 int index = variable.getIndex(); 519 if (holder) { 520 cv.visitVarInsn(ALOAD, index); 521 cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;"); 522 } else { 523 cv.visitVarInsn(ALOAD, index); // todo? shall xload based on the type? 524 } 525 } 526 527 public void storeVar(Variable variable, boolean holder) { 528 String type = variable.getTypeName(); 529 int index = variable.getIndex(); 530 531 if (holder) { 532 //int tempIndex = visitASTOREInTemp("reference", type); 533 cv.visitVarInsn(ALOAD, index); 534 cv.visitInsn(SWAP); // assuming the value on stack is single word 535 //cv.visitVarInsn(ALOAD, tempIndex); 536 cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V"); 537 } 538 else { 539 store(variable.deriveBoxedVersion()); // todo br seems right hand values on the stack are always object refs, primitives boxed 540 // if (!varStored) { 541 // //visitVariableStartLabel(variable); 542 // varStored = true; 543 // } 544 } 545 } 546 547 // private int visitASTOREInTemp(String name, String type) { 548 // Variable var = defineVariable(createVariableName(name), type, false); 549 // int varIdx = var.getIndex(); 550 // cv.visitVarInsn(ASTORE, varIdx); 551 // if (CREATE_DEBUG_INFO) cv.visitLabel(var.getStartLabel()); 552 // return varIdx; 553 // } 554 555 public void putField(FieldNode fld) { 556 putField(fld, getClassInternalName(fld.getOwner())); 557 } 558 559 public void putField(FieldNode fld, String ownerName) { 560 cv.visitFieldInsn(PUTFIELD, ownerName, fld.getName(), getTypeDescription(fld.getType().getName())); 561 } 562 563 public void loadThis() { 564 cv.visitVarInsn(ALOAD, 0); 565 } 566 567 public static ClassNode boxOnPrimitive(ClassNode type) { 568 if (!type.isArray()) return ClassHelper.getWrapper(type); 569 return boxOnPrimitive(type.getComponentType()).makeArray(); 570 } 571 572 /** 573 * convert boolean to Boolean 574 */ 575 public void boxBoolean() { 576 Label l0 = new Label(); 577 cv.visitJumpInsn(IFEQ, l0); 578 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;"); 579 Label l1 = new Label(); 580 cv.visitJumpInsn(GOTO, l1); 581 cv.visitLabel(l0); 582 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;"); 583 cv.visitLabel(l1); 584 } 585 586 /** 587 * load a message on the stack and remove it right away. Good for put a mark in the generated bytecode for debugging purpose. 588 * @param msg 589 */ 590 public void mark(String msg) { 591 cv.visitLdcInsn(msg); 592 cv.visitInsn(POP); 593 } 594 595 /** 596 * returns a name that Class.forName() can take. Notablely for arrays: 597 * [I, [Ljava.lang.String; etc 598 * Regular object type: java.lang.String 599 * @param name 600 * @return 601 */ 602 public static String formatNameForClassLoading(String name) { 603 if (name.equals("int") 604 || name.equals("long") 605 || name.equals("short") 606 || name.equals("float") 607 || name.equals("double") 608 || name.equals("byte") 609 || name.equals("char") 610 || name.equals("boolean") 611 || name.equals("void") 612 ) { 613 return name; 614 } 615 616 if (name == null) { 617 return "java.lang.Object;"; 618 } 619 620 if (name.startsWith("[")) { 621 return name.replace('/', '.'); 622 } 623 624 if (name.startsWith("L")) { 625 name = name.substring(1); 626 if (name.endsWith(";")) { 627 name = name.substring(0, name.length() - 1); 628 } 629 return name.replace('/', '.'); 630 } 631 632 String prefix = ""; 633 if (name.endsWith("[]")) { // todo need process multi 634 prefix = "["; 635 name = name.substring(0, name.length() - 2); 636 if (name.equals("int")) { 637 return prefix + "I"; 638 } 639 else if (name.equals("long")) { 640 return prefix + "J"; 641 } 642 else if (name.equals("short")) { 643 return prefix + "S"; 644 } 645 else if (name.equals("float")) { 646 return prefix + "F"; 647 } 648 else if (name.equals("double")) { 649 return prefix + "D"; 650 } 651 else if (name.equals("byte")) { 652 return prefix + "B"; 653 } 654 else if (name.equals("char")) { 655 return prefix + "C"; 656 } 657 else if (name.equals("boolean")) { 658 return prefix + "Z"; 659 } 660 else { 661 return prefix + "L" + name.replace('/', '.') + ";"; 662 } 663 } 664 return name.replace('/', '.'); 665 666 } 667 668 public void dup() { 669 cv.visitInsn(DUP); 670 } 671 }