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