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