View Javadoc

1   /*
2    $Id: BytecodeHelper.java,v 1.22 2005/11/13 16:42:11 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.22 $
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.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 }