1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package org.codehaus.groovy.classgen;
47
48 import groovy.lang.MetaMethod;
49
50 import java.lang.reflect.Method;
51 import java.lang.reflect.Modifier;
52 import java.math.BigDecimal;
53 import java.math.BigInteger;
54
55 import org.codehaus.groovy.ast.FieldNode;
56 import org.codehaus.groovy.ast.Parameter;
57 import org.codehaus.groovy.runtime.InvokerHelper;
58 import org.objectweb.asm.CodeVisitor;
59 import org.objectweb.asm.Constants;
60 import org.objectweb.asm.Label;
61
62
63 /***
64 * A helper class for bytecode generation with AsmClassGenerator2.
65 *
66 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
67 * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
68 * @version $Revision: 1.14 $
69 */
70 public class BytecodeHelper implements Constants {
71
72 private CodeVisitor cv;
73
74 public CodeVisitor getCodeVisitor() {
75 return cv;
76 }
77
78 public BytecodeHelper(CodeVisitor cv) {
79 this.cv = cv;
80 }
81
82 /***
83 * Generates the bytecode to autobox the current value on the stack
84 */
85 public void box(Class type) {
86 if (type.isPrimitive() && type != void.class) {
87 String returnString = "(" + getTypeDescription(type.getName()) + ")Ljava/lang/Object;";
88 cv.visitMethodInsn(INVOKESTATIC, getClassInternalName(InvokerHelper.class.getName()), "box", returnString);
89 }
90 }
91
92 /***
93 * box the primitive value on the stack
94 * @param cls
95 */
96 public void quickBoxIfNecessary(Class cls) {
97 String type = cls.getName();
98 String descr = getTypeDescription(type);
99 if (cls == boolean.class) {
100 boxBoolean();
101 }
102 else if (cls.isPrimitive() && cls != void.class) {
103
104 if (cls == Integer.TYPE) {
105 cv.visitMethodInsn(
106 INVOKESTATIC,
107 getClassInternalName(InvokerHelper.class.getName()),
108 "integerValue",
109 "(I)Ljava/lang/Integer;"
110 );
111 return;
112 }
113
114 String wrapperName = getObjectTypeForPrimitive(type);
115 String internName = getClassInternalName(wrapperName);
116 cv.visitTypeInsn(NEW, internName);
117 cv.visitInsn(DUP);
118 if (type.equals("double") || type.equals("long")) {
119 cv.visitInsn(DUP2_X2);
120 cv.visitInsn(POP2);
121 } else {
122 cv.visitInsn(DUP2_X1);
123 cv.visitInsn(POP2);
124 }
125 cv.visitMethodInsn(INVOKESPECIAL, internName, "<init>", "(" + descr + ")V");
126
127
128
129
130 }
131 }
132
133 /***
134 * unbox the ref on the stack
135 * @param cls
136 */
137 public void quickUnboxIfNecessary(Class cls) {
138 String type = cls.getName();
139
140 if (cls.isPrimitive() && cls != void.class) {
141 String wrapperName = getObjectTypeForPrimitive(type);
142 String internName = getClassInternalName(wrapperName);
143 if (cls == boolean.class) {
144 cv.visitTypeInsn(CHECKCAST, internName);
145 cv.visitMethodInsn(INVOKEVIRTUAL, internName, type + "Value", "()" + getTypeDescription(type));
146 } else {
147 cv.visitTypeInsn(CHECKCAST, "java/lang/Number");
148 cv.visitMethodInsn(INVOKEVIRTUAL,
149 }
150 }
151
152 }
153
154 public void box(String type) {
155 if (isPrimitiveType(type) && !type.equals("void")) {
156 String returnString = "(" + getTypeDescription(type) + ")Ljava/lang/Object;";
157 cv.visitMethodInsn(INVOKESTATIC, getClassInternalName(InvokerHelper.class.getName()), "box", returnString);
158
159 }
160 }
161
162 /***
163 * Generates the bytecode to unbox the current value on the stack
164 */
165 public void unbox(Class type) {
166 if (type.isPrimitive() && type != void.class) {
167 String returnString = "(Ljava/lang/Object;)" + getTypeDescription(type.getName());
168 cv.visitMethodInsn(
169 INVOKESTATIC,
170 getClassInternalName(InvokerHelper.class.getName()),
171 type.getName() + "Unbox",
172 returnString);
173 }
174 }
175
176 /***
177 * Generates the bytecode to unbox the current value on the stack
178 */
179 public void unbox(String type) {
180 if (isPrimitiveType(type) && !type.equals("void")) {
181 String returnString = "(Ljava/lang/Object;)" + getTypeDescription(type);
182 cv.visitMethodInsn(INVOKESTATIC, getClassInternalName(InvokerHelper.class.getName()), type + "Unbox", returnString);
183 }
184 }
185
186 public static boolean isPrimitiveType(String type) {
187 return type != null
188 && (type.equals("boolean")
189 || type.equals("byte")
190 || type.equals("char")
191 || type.equals("short")
192 || type.equals("int")
193 || type.equals("long")
194 || type.equals("float")
195 || type.equals("double"));
196 }
197
198 /***
199 * array types are special:
200 * eg.: String[]: classname: [Ljava/lang/String;
201 * int[]: [I
202 * @return the ASM type description
203 */
204 public static String getTypeDescription(String name) {
205
206
207 if (name == null) {
208 return "Ljava/lang/Object;";
209 }
210 if (name.equals("void")) {
211 return "V";
212 }
213
214 if (name.startsWith("[")) {
215 return name.replace('.', '/');
216 }
217
218 String prefix = "";
219 if (name.endsWith("[]")) {
220 prefix = "[";
221 name = name.substring(0, name.length() - 2);
222 }
223
224 if (name.equals("int")) {
225 return prefix + "I";
226 }
227 else if (name.equals("long")) {
228 return prefix + "J";
229 }
230 else if (name.equals("short")) {
231 return prefix + "S";
232 }
233 else if (name.equals("float")) {
234 return prefix + "F";
235 }
236 else if (name.equals("double")) {
237 return prefix + "D";
238 }
239 else if (name.equals("byte")) {
240 return prefix + "B";
241 }
242 else if (name.equals("char")) {
243 return prefix + "C";
244 }
245 else if (name.equals("boolean")) {
246 return prefix + "Z";
247 }
248 return prefix + "L" + name.replace('.', '/') + ";";
249 }
250
251 /***
252 * @return the ASM internal name of the type
253 */
254 public static String getClassInternalName(String name) {
255 if (name == null) {
256 return "java/lang/Object";
257 }
258 String answer = name.replace('.', '/');
259 if (answer.endsWith("[]")) {
260 return "[" + answer.substring(0, answer.length() - 2);
261 }
262 return answer;
263 }
264
265 /***
266 * @return the regular class name of the type
267 */
268 public static String getClassRegularName(String name) {
269 if (name == null) {
270 return "java.lang.Object";
271 }
272 if (name.startsWith("L")) {
273 name = name.substring(1);
274 if (name.endsWith(";"))
275 name = name.substring(0, name.length() - 1);
276 }
277 String answer = name.replace('/', '.');
278 return answer;
279 }
280
281 /***
282 * @return the ASM method type descriptor
283 */
284 public static String getMethodDescriptor(String returnTypeName, Parameter[] paramTypeNames) {
285
286 StringBuffer buffer = new StringBuffer("(");
287 for (int i = 0; i < paramTypeNames.length; i++) {
288 buffer.append(getTypeDescription(paramTypeNames[i].getType()));
289 }
290 buffer.append(")");
291 buffer.append(getTypeDescription(returnTypeName));
292 return buffer.toString();
293 }
294
295 /***
296 * @return the ASM method type descriptor
297 */
298 public static String getMethodDescriptor(Class returnType, Class[] paramTypes) {
299
300 StringBuffer buffer = new StringBuffer("(");
301 for (int i = 0; i < paramTypes.length; i++) {
302 buffer.append(getTypeDescription(paramTypes[i]));
303 }
304 buffer.append(")");
305 buffer.append(getTypeDescription(returnType));
306 return buffer.toString();
307 }
308
309 public static String getMethodDescriptor(Method meth) {
310 return getMethodDescriptor(meth.getReturnType(), meth.getParameterTypes());
311 }
312
313 public static String getTypeDescription(Class type) {
314 if (type.isArray()) {
315 return type.getName().replace('.', '/');
316 }
317 else {
318 return getTypeDescription(type.getName());
319 }
320 }
321
322 /***
323 * @return an array of ASM internal names of the type
324 */
325 public static String[] getClassInternalNames(String[] names) {
326 int size = names.length;
327 String[] answer = new String[size];
328 for (int i = 0; i < size; i++) {
329 answer[i] = getClassInternalName(names[i]);
330 }
331 return answer;
332 }
333
334 protected void pushConstant(boolean value) {
335 if (value) {
336 cv.visitInsn(ICONST_1);
337 }
338 else {
339 cv.visitInsn(ICONST_0);
340 }
341 }
342
343 protected void pushConstant(int value) {
344 switch (value) {
345 case 0 :
346 cv.visitInsn(ICONST_0);
347 break;
348 case 1 :
349 cv.visitInsn(ICONST_1);
350 break;
351 case 2 :
352 cv.visitInsn(ICONST_2);
353 break;
354 case 3 :
355 cv.visitInsn(ICONST_3);
356 break;
357 case 4 :
358 cv.visitInsn(ICONST_4);
359 break;
360 case 5 :
361 cv.visitInsn(ICONST_5);
362 break;
363 default :
364 if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
365 cv.visitIntInsn(BIPUSH, value);
366 }
367 else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
368 cv.visitIntInsn(SIPUSH, value);
369 }
370 else {
371 cv.visitLdcInsn(new Integer(value));
372 }
373 }
374 }
375
376 public void doCast(String type) {
377 if (!type.equals("java.lang.Object")) {
378 if (isPrimitiveType(type) && !type.equals("void")) {
379 unbox(type);
380 }
381 else {
382 cv.visitTypeInsn(
383 CHECKCAST,
384 type.endsWith("[]") ? getTypeDescription(type) : getClassInternalName(type));
385 }
386 }
387 }
388
389 public void doCast(Class type) {
390 String name = type.getName();
391 if (type.isArray()) {
392 name = type.getComponentType().getName() + "[]";
393 }
394 doCast(name);
395 }
396
397 public void load(String type, int idx) {
398 if (type.equals("double")) {
399 cv.visitVarInsn(DLOAD, idx);
400 }
401 else if (type.equals("float")) {
402 cv.visitVarInsn(FLOAD, idx);
403 }
404 else if (type.equals("long")) {
405 cv.visitVarInsn(LLOAD, idx);
406 }
407 else if (
408 type.equals("boolean")
409 || type.equals("char")
410 || type.equals("byte")
411 || type.equals("int")
412 || type.equals("short")) {
413 cv.visitVarInsn(ILOAD, idx);
414 }
415 else {
416 cv.visitVarInsn(ALOAD, idx);
417 }
418 }
419
420 public void load(Variable v) {
421 load(v.getTypeName(), v.getIndex());
422 }
423
424 public void store(String type, int idx) {
425 if (type.equals("double")) {
426 cv.visitVarInsn(DSTORE, idx);
427 }
428 else if (type.equals("float")) {
429 cv.visitVarInsn(FSTORE, idx);
430 }
431 else if (type.equals("long")) {
432 cv.visitVarInsn(LSTORE, idx);
433 }
434 else if (
435 type.equals("boolean")
436 || type.equals("char")
437 || type.equals("byte")
438 || type.equals("int")
439 || type.equals("short")) {
440 cv.visitVarInsn(ISTORE, idx);
441 }
442 else {
443 cv.visitVarInsn(ASTORE, idx);
444 }
445 }
446
447 public void store(Variable v, boolean markStart) {
448 String type = v.getTypeName();
449 int idx = v.getIndex();
450
451 if (type.equals("double")) {
452 cv.visitVarInsn(DSTORE, idx);
453 }
454 else if (type.equals("float")) {
455 cv.visitVarInsn(FSTORE, idx);
456 }
457 else if (type.equals("long")) {
458 cv.visitVarInsn(LSTORE, idx);
459 }
460 else if (
461 type.equals("boolean")
462 || type.equals("char")
463 || type.equals("byte")
464 || type.equals("int")
465 || type.equals("short")) {
466 cv.visitVarInsn(ISTORE, idx);
467 }
468 else {
469 cv.visitVarInsn(ASTORE, idx);
470 }
471 if (AsmClassGenerator2.CREATE_DEBUG_INFO && markStart) {
472 Label l = v.getStartLabel();
473 if (l != null) {
474 cv.visitLabel(l);
475 } else {
476 System.out.println("start label == null! what to do about this?");
477 }
478 }
479 }
480
481 public void store(Variable v) {
482 store(v, false);
483 }
484
485
486 public static String getObjectTypeForPrimitive(String type) {
487 if (type.equals("boolean")) {
488 return Boolean.class.getName();
489 }
490 else if (type.equals("byte")) {
491 return Byte.class.getName();
492 }
493 else if (type.equals("char")) {
494 return Character.class.getName();
495 }
496 else if (type.equals("short")) {
497 return Short.class.getName();
498 }
499 else if (type.equals("int")) {
500 return Integer.class.getName();
501 }
502 else if (type.equals("long")) {
503 return Long.class.getName();
504 }
505 else if (type.equals("float")) {
506 return Float.class.getName();
507 }
508 else if (type.equals("double")) {
509 return Double.class.getName();
510 }
511 else {
512 return type;
513 }
514 }
515
516 /***
517 *
518 * @param type "int[]" etc
519 * @return "[I" format
520 */
521 public static String getObjectArrayTypeForPrimitiveArray(String type) {
522 String prefix = "";
523 while (type.endsWith("[]")) {
524 prefix += "[";
525 type = type.substring(0, type.length() - 2);
526 }
527
528 if (prefix.length() ==0)
529 return type;
530
531 String suffix = getObjectTypeForPrimitive(type);
532 return prefix + "L" + suffix + ";";
533 }
534
535
536
537 /***
538 * load the constant on the operand stack. primitives auto-boxed.
539 */
540 void loadConstant (Object value) {
541 if (value == null) {
542 cv.visitInsn(ACONST_NULL);
543 }
544 else if (value instanceof String) {
545 cv.visitLdcInsn(value);
546 }
547 else if (value instanceof Number) {
548 /*** todo it would be more efficient to generate class constants */
549 Number n = (Number) value;
550 String className = BytecodeHelper.getClassInternalName(value.getClass().getName());
551 cv.visitTypeInsn(NEW, className);
552 cv.visitInsn(DUP);
553 String methodType;
554 if (n instanceof Double) {
555 cv.visitLdcInsn(n);
556 methodType = "(D)V";
557 }
558 else if (n instanceof Float) {
559 cv.visitLdcInsn(n);
560 methodType = "(F)V";
561 }
562 else if (n instanceof Long) {
563 cv.visitLdcInsn(n);
564 methodType = "(J)V";
565 }
566 else if (n instanceof BigDecimal) {
567 cv.visitLdcInsn(n.toString());
568 methodType = "(Ljava/lang/String;)V";
569 }
570 else if (n instanceof BigInteger) {
571 cv.visitLdcInsn(n.toString());
572 methodType = "(Ljava/lang/String;)V";
573 }
574 else if (n instanceof Integer){
575
576 pushConstant(n.intValue());
577 methodType = "(I)V";
578 }
579 else
580 {
581 throw new ClassGeneratorException(
582 "Cannot generate bytecode for constant: " + value
583 + " of type: " + value.getClass().getName()
584 +". Numeric constant type not supported.");
585 }
586 cv.visitMethodInsn(INVOKESPECIAL, className, "<init>", methodType);
587 }
588 else if (value instanceof Boolean) {
589 Boolean bool = (Boolean) value;
590 String text = (bool.booleanValue()) ? "TRUE" : "FALSE";
591 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", text, "Ljava/lang/Boolean;");
592 }
593 else if (value instanceof Class) {
594 Class vc = (Class) value;
595 if (vc.getName().equals("java.lang.Void")) {
596
597 } else {
598 throw new ClassGeneratorException(
599 "Cannot generate bytecode for constant: " + value + " of type: " + value.getClass().getName());
600 }
601 }
602 else {
603 throw new ClassGeneratorException(
604 "Cannot generate bytecode for constant: " + value + " of type: " + value.getClass().getName());
605 }
606 }
607
608
609 /***
610 * load the value of the variable on the operand stack. unbox it if it's a reference
611 * @param variable
612 * @param holder
613 */
614 public void loadVar(Variable variable, boolean holder) {
615 String type = variable.getTypeName();
616 int index = variable.getIndex();
617 if (holder) {
618 cv.visitVarInsn(ALOAD, index);
619 cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;");
620 } else {
621 cv.visitVarInsn(ALOAD, index);
622 }
623 }
624
625 public void storeVar(Variable variable, boolean holder) {
626 String type = variable.getTypeName();
627 int index = variable.getIndex();
628
629 if (holder) {
630
631 cv.visitVarInsn(ALOAD, index);
632 cv.visitInsn(SWAP);
633
634 cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V");
635 }
636 else {
637 store(variable.deriveBoxedVersion());
638
639
640
641
642 }
643 }
644
645
646
647
648
649
650
651
652
653 public void putField(FieldNode fld) {
654 putField(fld, getClassInternalName(fld.getOwner()));
655 }
656
657 public void putField(FieldNode fld, String ownerName) {
658 cv.visitFieldInsn(PUTFIELD, ownerName, fld.getName(), getTypeDescription(fld.getType()));
659 }
660
661 public void loadThis() {
662 cv.visitVarInsn(ALOAD, 0);
663 }
664
665 public static Class boxOnPrimitive(Class cls) {
666 Class ans = cls;
667 if (ans == null)
668 return null;
669
670 if (cls.isPrimitive() && cls != void.class) {
671 if (cls == int.class) {
672 ans = Integer.class;
673 }
674 else if (cls == byte.class) {
675 ans = Byte.class;
676 }
677 else if (cls == char.class) {
678 ans = Character.class;
679 }
680 else if (cls == short.class) {
681 ans = Short.class;
682 }
683 else if (cls == boolean.class) {
684 ans = Boolean.class;
685 }
686 else if (cls == float.class) {
687 ans = Float.class;
688 }
689 else if (cls == long.class) {
690 ans = Long.class;
691 }
692 else if (cls == double.class) {
693 ans = Double.class;
694 }
695 }
696 else if (cls.isArray()){
697
698 int dimension = 0;
699 Class elemType = null;
700 do {
701 ++dimension;
702 elemType = cls.getComponentType();
703 } while(elemType.isArray());
704
705 if (elemType.isPrimitive()) {
706 Class boxElem = null;
707 if (elemType == int.class) {
708 boxElem = Integer.class;
709 }
710 else if (elemType == byte.class) {
711 boxElem = Byte.class;
712 }
713 else if (elemType == char.class) {
714 boxElem = Character.class;
715 }
716 else if (elemType == short.class) {
717 boxElem = Short.class;
718 }
719 else if (elemType == boolean.class) {
720 boxElem = Boolean.class;
721 }
722 else if (elemType == float.class) {
723 boxElem = Float.class;
724 }
725 else if (elemType == long.class) {
726 boxElem = Long.class;
727 }
728 else if (elemType == double.class) {
729 boxElem = Double.class;
730 }
731
732 String typeName = "";
733 for (int i = 0; i < dimension; i++){
734 typeName += "[";
735 }
736 typeName += "L" + boxElem.getName() + ";";
737 try {
738 return Class.forName(typeName);
739 } catch (ClassNotFoundException e) {
740 throw new RuntimeException(e);
741 }
742 }
743 }
744 return ans;
745 }
746
747 /***
748 * create the bytecode to invoke a method
749 * @param meth the method object to invoke
750 */
751 public void invoke(Method meth) {
752 int op = Modifier.isStatic(meth.getModifiers()) ?
753 INVOKESTATIC :
754 (meth.getDeclaringClass().isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL);
755
756 cv.visitMethodInsn(
757 op,
758 getClassInternalName(meth.getDeclaringClass().getName()),
759 meth.getName(),
760 getMethodDescriptor(meth)
761 );
762 }
763
764 /***
765 * convert boolean to Boolean
766 */
767 public void boxBoolean() {
768 Label l0 = new Label();
769 cv.visitJumpInsn(IFEQ, l0);
770 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
771 Label l1 = new Label();
772 cv.visitJumpInsn(GOTO, l1);
773 cv.visitLabel(l0);
774 cv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;");
775 cv.visitLabel(l1);
776 }
777
778 public static String getMethodDescriptor(MetaMethod metamethod) {
779 return getMethodDescriptor(metamethod.getReturnType(), metamethod.getParameterTypes());
780 }
781
782 /***
783 * load a message on the stack and remove it right away. Good for put a mark in the generated bytecode for debugging purpose.
784 * @param msg
785 */
786 public void mark(String msg) {
787 cv.visitLdcInsn(msg);
788 cv.visitInsn(POP);
789 }
790
791 /***
792 * returns a name that Class.forName() can take. Notablely for arrays:
793 * [I, [Ljava.lang.String; etc
794 * Regular object type: java.lang.String
795 * @param name
796 * @return
797 */
798 public static String formatNameForClassLoading(String name) {
799 if (name.equals("int")
800 || name.equals("long")
801 || name.equals("short")
802 || name.equals("float")
803 || name.equals("double")
804 || name.equals("byte")
805 || name.equals("char")
806 || name.equals("boolean")
807 || name.equals("void")
808 ) {
809 return name;
810 }
811
812 if (name == null) {
813 return "java.lang.Object;";
814 }
815
816 if (name.startsWith("[")) {
817 return name.replace('/', '.');
818 }
819
820 if (name.startsWith("L")) {
821 name = name.substring(1);
822 if (name.endsWith(";")) {
823 name = name.substring(0, name.length() - 1);
824 }
825 return name.replace('/', '.');
826 }
827
828 String prefix = "";
829 if (name.endsWith("[]")) {
830 prefix = "[";
831 name = name.substring(0, name.length() - 2);
832 if (name.equals("int")) {
833 return prefix + "I";
834 }
835 else if (name.equals("long")) {
836 return prefix + "J";
837 }
838 else if (name.equals("short")) {
839 return prefix + "S";
840 }
841 else if (name.equals("float")) {
842 return prefix + "F";
843 }
844 else if (name.equals("double")) {
845 return prefix + "D";
846 }
847 else if (name.equals("byte")) {
848 return prefix + "B";
849 }
850 else if (name.equals("char")) {
851 return prefix + "C";
852 }
853 else if (name.equals("boolean")) {
854 return prefix + "Z";
855 }
856 else {
857 return prefix + "L" + name.replace('/', '.') + ";";
858 }
859 }
860 return name.replace('/', '.');
861
862 }
863
864 public void dup() {
865 cv.visitInsn(DUP);
866 }
867 }