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