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 }