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