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