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    }