View Javadoc

1   /*
2    $Id: BytecodeHelper.java,v 1.30 2006/06/25 18:52:00 blackdrag Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package org.codehaus.groovy.classgen;
47  
48  import java.math.BigDecimal;
49  import java.math.BigInteger;
50  
51  import org.codehaus.groovy.ast.ClassHelper;
52  import org.codehaus.groovy.ast.ClassNode;
53  import org.codehaus.groovy.ast.FieldNode;
54  import org.codehaus.groovy.ast.Parameter;
55  import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
56  import org.objectweb.asm.MethodVisitor;
57  import org.objectweb.asm.Opcodes;
58  import org.objectweb.asm.Label;
59  
60  /***
61   * A helper class for bytecode generation with AsmClassGenerator.
62   * 
63   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
64   * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
65   * @version $Revision: 1.30 $
66   */
67  public class BytecodeHelper implements Opcodes {
68  
69      private MethodVisitor cv;
70  
71      public MethodVisitor getMethodVisitor() {
72          return cv;
73      }
74  
75      public BytecodeHelper(MethodVisitor cv) {
76          this.cv = cv;
77      }
78      
79      /***
80       * box the primitive value on the stack
81       * @param cls
82       */
83      public void quickBoxIfNecessary(ClassNode type) {
84          String descr = getTypeDescription(type);
85          if (type == ClassHelper.boolean_TYPE) {
86              boxBoolean();
87          }
88          else if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) {
89              // use a special integer pool in the invokerhelper
90              if (type == ClassHelper.int_TYPE) {
91                  cv.visitMethodInsn(
92                          INVOKESTATIC,
93                          getClassInternalName(ScriptBytecodeAdapter.class.getName()),
94                          "integerValue",
95                          "(I)Ljava/lang/Integer;"
96                  );
97                  return;
98              }
99  
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) + ")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);
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     public static String getClassInternalName(ClassNode t){
167     	if (t.isPrimaryClassNode()){
168     		return getClassInternalName(t.getName());
169     	}
170         return getClassInternalName(t.getTypeClass());
171     }
172     
173     public static String getClassInternalName(Class t) {
174         return org.objectweb.asm.Type.getInternalName(t);
175     }
176     
177     /***
178      * @return the ASM internal name of the type
179      */
180     public static String getClassInternalName(String name) {
181         return name.replace('.', '/');
182     }
183     
184     /***
185      * @return the ASM method type descriptor
186      */
187     public static String getMethodDescriptor(ClassNode returnType, Parameter[] parameters) {
188         StringBuffer buffer = new StringBuffer("(");
189         for (int i = 0; i < parameters.length; i++) {
190             buffer.append(getTypeDescription(parameters[i].getType()));
191         }
192         buffer.append(")");
193         buffer.append(getTypeDescription(returnType));
194         return buffer.toString();
195     }
196 
197     /***
198      * @return the ASM method type descriptor
199      */
200     public static String getMethodDescriptor(Class returnType, Class[] paramTypes) {
201         // lets avoid class loading
202         StringBuffer buffer = new StringBuffer("(");
203         for (int i = 0; i < paramTypes.length; i++) {
204             buffer.append(getTypeDescription(paramTypes[i]));
205         }
206         buffer.append(")");
207         buffer.append(getTypeDescription(returnType));
208         return buffer.toString();
209     }
210 
211     public static String getTypeDescription(Class c) {
212         return org.objectweb.asm.Type.getDescriptor(c);
213     }
214     
215     /***
216      * array types are special:
217      * eg.: String[]: classname: [Ljava.lang.String;
218      *      Object:   classname: java.lang.Object
219      *      int[] :   classname: [I
220      * unlike getTypeDescription '.' is not replaces by '/'. 
221      * it seems that makes problems for
222      * the class loading if '.' is replaced by '/'
223      * @return the ASM type description for class loading
224      */
225     public static String getClassLoadingTypeDescription(ClassNode c) {
226         StringBuffer buf = new StringBuffer();
227         boolean array = false;
228         while (true) {
229             if (c.isArray()) {
230                 buf.append('[');
231                 c = c.getComponentType();
232                 array = true;
233             } else {
234                 if (ClassHelper.isPrimitiveType(c)) {
235                     buf.append(getTypeDescription(c));
236                 } else {
237                     if (array) buf.append('L');
238                     buf.append(c.getName());
239                     if(array) buf.append(';');
240                 }
241                 return buf.toString();
242             }
243         }
244     }
245     
246     /***
247      * array types are special:
248      * eg.: String[]: classname: [Ljava/lang/String;
249      *      int[]: [I
250      * @return the ASM type description
251      */
252     public static String getTypeDescription(ClassNode c) {
253         StringBuffer buf = new StringBuffer();
254         ClassNode d = c;
255         while (true) {
256             if (ClassHelper.isPrimitiveType(d)) {
257                 char car;
258                 if (d == ClassHelper.int_TYPE) {
259                     car = 'I';
260                 } else if (d == ClassHelper.VOID_TYPE) {
261                     car = 'V';
262                 } else if (d == ClassHelper.boolean_TYPE) {
263                     car = 'Z';
264                 } else if (d == ClassHelper.byte_TYPE) {
265                     car = 'B';
266                 } else if (d == ClassHelper.char_TYPE) {
267                     car = 'C';
268                 } else if (d == ClassHelper.short_TYPE) {
269                     car = 'S';
270                 } else if (d == ClassHelper.double_TYPE) {
271                     car = 'D';
272                 } else if (d == ClassHelper.float_TYPE) {
273                     car = 'F';
274                 } else /* long */{
275                     car = 'J';
276                 }
277                 buf.append(car);
278                 return buf.toString();
279             } else if (d.isArray()) {
280                 buf.append('[');
281                 d = d.getComponentType();
282             } else {
283                 buf.append('L');
284                 String name = d.getName();
285                 int len = name.length();
286                 for (int i = 0; i < len; ++i) {
287                     char car = name.charAt(i);
288                     buf.append(car == '.' ? '/' : car);
289                 }
290                 buf.append(';');
291                 return buf.toString();
292             }
293         }
294     }
295 
296     /***
297      * @return an array of ASM internal names of the type
298      */
299     public static String[] getClassInternalNames(ClassNode[] names) {
300         int size = names.length;
301         String[] answer = new String[size];
302         for (int i = 0; i < size; i++) {
303             answer[i] = getClassInternalName(names[i]);
304         }
305         return answer;
306     }
307 
308     protected void pushConstant(boolean value) {
309         if (value) {
310             cv.visitInsn(ICONST_1);
311         }
312         else {
313             cv.visitInsn(ICONST_0);
314         }
315     }
316 
317     protected void pushConstant(int value) {
318         switch (value) {
319             case 0 :
320                 cv.visitInsn(ICONST_0);
321                 break;
322             case 1 :
323                 cv.visitInsn(ICONST_1);
324                 break;
325             case 2 :
326                 cv.visitInsn(ICONST_2);
327                 break;
328             case 3 :
329                 cv.visitInsn(ICONST_3);
330                 break;
331             case 4 :
332                 cv.visitInsn(ICONST_4);
333                 break;
334             case 5 :
335                 cv.visitInsn(ICONST_5);
336                 break;
337             default :
338                 if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
339                     cv.visitIntInsn(BIPUSH, value);
340                 }
341                 else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
342                     cv.visitIntInsn(SIPUSH, value);
343                 }
344                 else {
345                     cv.visitLdcInsn(new Integer(value));
346                 }
347         }
348     }
349 
350     public void doCast(Class type) {
351         if (type!=Object.class) {
352             if (type.isPrimitive() && type!=Void.TYPE) {
353                 unbox(type);
354             }
355             else {
356                 cv.visitTypeInsn(
357                     CHECKCAST,
358                     type.isArray() ? getTypeDescription(type) : getClassInternalName(type.getName()));
359             }
360         }
361     }
362     
363     public void doCast(ClassNode type) {
364         if (type==ClassHelper.OBJECT_TYPE) return;
365         if (ClassHelper.isPrimitiveType(type) && type!=ClassHelper.VOID_TYPE) {
366             unbox(type);
367         }
368         else {
369             cv.visitTypeInsn(
370                     CHECKCAST,
371                     type.isArray() ? getTypeDescription(type) : getClassInternalName(type));
372         }
373     }
374 
375     public void load(ClassNode type, int idx) {
376         if (type==ClassHelper.double_TYPE) {
377             cv.visitVarInsn(DLOAD, idx);
378         }
379         else if (type==ClassHelper.float_TYPE) {
380             cv.visitVarInsn(FLOAD, idx);
381         }
382         else if (type==ClassHelper.long_TYPE) {
383             cv.visitVarInsn(LLOAD, idx);
384         }
385         else if (
386             type==ClassHelper.boolean_TYPE
387                 || type==ClassHelper.char_TYPE
388                 || type==ClassHelper.byte_TYPE
389                 || type==ClassHelper.int_TYPE
390                 || type==ClassHelper.short_TYPE)
391         {    
392             cv.visitVarInsn(ILOAD, idx);
393         }
394         else {
395             cv.visitVarInsn(ALOAD, idx);
396         }
397     }
398 
399     public void load(Variable v) {
400     	load(v.getType(), v.getIndex());
401     }
402 
403     public void store(Variable v, boolean markStart) {
404         ClassNode type = v.getType();
405         unbox(type);
406         int idx = v.getIndex();
407 
408         if (type==ClassHelper.double_TYPE) {
409             cv.visitVarInsn(DSTORE, idx);
410         }
411         else if (type==ClassHelper.float_TYPE) {
412             cv.visitVarInsn(FSTORE, idx);
413         }
414         else if (type==ClassHelper.long_TYPE) {
415             cv.visitVarInsn(LSTORE, idx);
416         }
417         else if (
418                 type==ClassHelper.boolean_TYPE
419                 || type==ClassHelper.char_TYPE
420                 || type==ClassHelper.byte_TYPE
421                 || type==ClassHelper.int_TYPE
422                 || type==ClassHelper.short_TYPE) {
423             cv.visitVarInsn(ISTORE, idx);
424         }
425         else {
426             cv.visitVarInsn(ASTORE, idx);
427         }
428     }
429 
430     public void store(Variable v) {
431         store(v, false);
432     }
433 
434     /***
435      * load the constant on the operand stack. primitives auto-boxed.
436      */
437     void loadConstant (Object value) {
438         if (value == null) {
439             cv.visitInsn(ACONST_NULL);
440         }
441         else if (value instanceof String) {
442             cv.visitLdcInsn(value);
443         }
444         else if (value instanceof Character) {
445             String className = "java/lang/Character";
446             cv.visitTypeInsn(NEW, className);
447             cv.visitInsn(DUP);
448             cv.visitLdcInsn(value);
449             String methodType = "(C)V";
450             cv.visitMethodInsn(INVOKESPECIAL, className, "<init>", methodType);
451         }
452         else if (value instanceof Number) {
453             /*** todo it would be more efficient to generate class constants */
454             Number n = (Number) value;
455             String className = BytecodeHelper.getClassInternalName(value.getClass().getName());
456             cv.visitTypeInsn(NEW, className);
457             cv.visitInsn(DUP);
458             String methodType;
459             if (n instanceof Integer) {
460             	//pushConstant(n.intValue());
461             	cv.visitLdcInsn(n);
462             	methodType = "(I)V";
463         	}
464             else if (n instanceof Double) {
465             	cv.visitLdcInsn(n);
466             	methodType = "(D)V";
467             }
468             else if (n instanceof Float) {
469             	cv.visitLdcInsn(n);
470             	methodType = "(F)V";
471             }
472             else if (n instanceof Long) {
473             	cv.visitLdcInsn(n);
474             	methodType = "(J)V";
475             }
476             else if (n instanceof BigDecimal) {
477             	cv.visitLdcInsn(n.toString());
478             	methodType = "(Ljava/lang/String;)V";
479             }
480             else if (n instanceof BigInteger) {
481             	cv.visitLdcInsn(n.toString());
482             	methodType = "(Ljava/lang/String;)V";
483             }
484             else if (n instanceof Short) {
485             	cv.visitLdcInsn(n);
486             	methodType = "(S)V";
487             }
488             else if (n instanceof Byte) {
489             	cv.visitLdcInsn(n);
490             	methodType = "(B)V";
491             }
492             else {
493         	throw new ClassGeneratorException(
494                                "Cannot generate bytecode for constant: " + value
495                              + " of type: " + value.getClass().getName()
496                              + ".  Numeric constant type not supported.");
497             }
498             cv.visitMethodInsn(INVOKESPECIAL, className, "<init>", methodType);
499         }
500         else if (value instanceof Boolean) {
501             Boolean bool = (Boolean) value;
502             String text = (bool.booleanValue()) ? "TRUE" : "FALSE";
503             cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", text, "Ljava/lang/Boolean;");
504         }
505         else if (value instanceof Class) {
506             Class vc = (Class) value;
507             if (vc.getName().equals("java.lang.Void")) {
508                 // load nothing here for void
509             } else {
510                 throw new ClassGeneratorException(
511                 "Cannot generate bytecode for constant: " + value + " of type: " + value.getClass().getName());
512             }
513         }
514         else {
515             throw new ClassGeneratorException(
516                 "Cannot generate bytecode for constant: " + value + " of type: " + value.getClass().getName());
517         }
518     }
519 
520 
521     /***
522      * load the value of the variable on the operand stack. unbox it if it's a reference
523      * @param variable
524      * @param holder
525      */
526     public void loadVar(Variable variable) {
527 		int index = variable.getIndex();
528 		if (variable.isHolder()) {
529 			cv.visitVarInsn(ALOAD, index);
530 			cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;");
531 		} else {
532             load(variable);
533             if (variable!=Variable.THIS_VARIABLE && variable!=Variable.SUPER_VARIABLE) {
534                 box(variable.getType());
535             }
536 		}
537 	}
538     
539     public void storeVar(Variable variable) {
540         String  type   = variable.getTypeName();
541         int     index  = variable.getIndex();
542         
543     	if (variable.isHolder()) {
544             cv.visitVarInsn(ALOAD, index);
545             cv.visitInsn(SWAP);  
546             cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V");
547         }
548         else {
549             store(variable,false);
550         }
551     }
552 
553     public void putField(FieldNode fld) {
554     	putField(fld, getClassInternalName(fld.getOwner()));
555     }
556 
557     public void putField(FieldNode fld, String ownerName) {
558     	cv.visitFieldInsn(PUTFIELD, ownerName, fld.getName(), getTypeDescription(fld.getType()));
559     }
560 
561     public void loadThis() {
562         cv.visitVarInsn(ALOAD, 0);
563     }
564     
565     public void swapObjectWith(ClassNode type) {
566         if (type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE) {
567             cv.visitInsn(DUP_X2);
568             cv.visitInsn(POP);
569         } else {
570             cv.visitInsn(SWAP);
571         }
572     }
573 
574     public static ClassNode boxOnPrimitive(ClassNode type) {
575         if (!type.isArray()) return ClassHelper.getWrapper(type);
576         return boxOnPrimitive(type.getComponentType()).makeArray();
577     }
578 
579     /***
580      * convert boolean to Boolean
581      */
582     public void boxBoolean() {
583         Label l0 = new Label();
584         cv.visitJumpInsn(IFEQ, l0);
585         cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
586         Label l1 = new Label();
587         cv.visitJumpInsn(GOTO, l1);
588         cv.visitLabel(l0);
589         cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;");
590         cv.visitLabel(l1);
591     }
592 
593     /***
594      * load a message on the stack and remove it right away. Good for put a mark in the generated bytecode for debugging purpose.
595      * @param msg
596      */
597     public void mark(String msg) {
598         cv.visitLdcInsn(msg);
599         cv.visitInsn(POP);
600     }
601     
602     /***
603      * returns a name that Class.forName() can take. Notablely for arrays:
604      * [I, [Ljava.lang.String; etc
605      * Regular object type:  java.lang.String
606      * @param name
607      * @return
608      */
609     public static String formatNameForClassLoading(String name) {
610         if (name.equals("int")
611         		|| name.equals("long")
612 				|| name.equals("short")
613 				|| name.equals("float")
614 				|| name.equals("double")
615 				|| name.equals("byte")
616 				|| name.equals("char")
617 				|| name.equals("boolean")
618 				|| name.equals("void")
619         	) {
620             return name;
621         }
622 
623         if (name == null) {
624             return "java.lang.Object;";
625         }
626 
627         if (name.startsWith("[")) {
628             return name.replace('/', '.');
629         }
630         
631         if (name.startsWith("L")) {
632         	name = name.substring(1);
633         	if (name.endsWith(";")) {
634         		name = name.substring(0, name.length() - 1);
635         	}
636         	return name.replace('/', '.');
637         }
638 
639         String prefix = "";
640         if (name.endsWith("[]")) { // todo need process multi
641             prefix = "[";
642             name = name.substring(0, name.length() - 2);
643             if (name.equals("int")) {
644                 return prefix + "I";
645             }
646             else if (name.equals("long")) {
647                 return prefix + "J";
648             }
649             else if (name.equals("short")) {
650                 return prefix + "S";
651             }
652             else if (name.equals("float")) {
653                 return prefix + "F";
654             }
655             else if (name.equals("double")) {
656                 return prefix + "D";
657             }
658             else if (name.equals("byte")) {
659                 return prefix + "B";
660             }
661             else if (name.equals("char")) {
662                 return prefix + "C";
663             }
664             else if (name.equals("boolean")) {
665                 return prefix + "Z";
666             }
667             else {
668             	return prefix + "L" + name.replace('/', '.') + ";";
669             }
670         }
671         return name.replace('/', '.');
672 
673     }
674 
675     public void dup() {
676         cv.visitInsn(DUP);
677     }
678     
679 }