001 /*
002 $Id: BytecodeHelper.java,v 1.22 2005/11/13 16:42:11 blackdrag Exp $
003
004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005
006 Redistribution and use of this software and associated documentation
007 ("Software"), with or without modification, are permitted provided
008 that the following conditions are met:
009
010 1. Redistributions of source code must retain copyright
011 statements and notices. Redistributions must also contain a
012 copy of this document.
013
014 2. Redistributions in binary form must reproduce the
015 above copyright notice, this list of conditions and the
016 following disclaimer in the documentation and/or other
017 materials provided with the distribution.
018
019 3. The name "groovy" must not be used to endorse or promote
020 products derived from this Software without prior written
021 permission of The Codehaus. For written permission,
022 please contact info@codehaus.org.
023
024 4. Products derived from this Software may not be called "groovy"
025 nor may "groovy" appear in their names without prior written
026 permission of The Codehaus. "groovy" is a registered
027 trademark of The Codehaus.
028
029 5. Due credit should be given to The Codehaus -
030 http://groovy.codehaus.org/
031
032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043 OF THE POSSIBILITY OF SUCH DAMAGE.
044
045 */
046 package org.codehaus.groovy.classgen;
047
048 import java.math.BigDecimal;
049 import java.math.BigInteger;
050
051 import org.codehaus.groovy.ast.ClassHelper;
052 import org.codehaus.groovy.ast.ClassNode;
053 import org.codehaus.groovy.ast.FieldNode;
054 import org.codehaus.groovy.ast.Parameter;
055 import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
056 import org.objectweb.asm.MethodVisitor;
057 import org.objectweb.asm.Opcodes;
058 import org.objectweb.asm.Label;
059
060 /**
061 * A helper class for bytecode generation with AsmClassGenerator.
062 *
063 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
064 * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
065 * @version $Revision: 1.22 $
066 */
067 public class BytecodeHelper implements Opcodes {
068
069 private MethodVisitor cv;
070
071 public MethodVisitor getMethodVisitor() {
072 return cv;
073 }
074
075 public BytecodeHelper(MethodVisitor cv) {
076 this.cv = cv;
077 }
078
079 /**
080 * box the primitive value on the stack
081 * @param cls
082 */
083 public void quickBoxIfNecessary(ClassNode type) {
084 String descr = getTypeDescription(type);
085 if (type == ClassHelper.boolean_TYPE) {
086 boxBoolean();
087 }
088 else if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) {
089 // use a special integer pool in the invokerhelper
090 if (type == ClassHelper.int_TYPE) {
091 cv.visitMethodInsn(
092 INVOKESTATIC,
093 getClassInternalName(ScriptBytecodeAdapter.class.getName()),
094 "integerValue",
095 "(I)Ljava/lang/Integer;"
096 );
097 return;
098 }
099
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 // Operand opr = new Operand(ITEM_Object, wrapperName, "", "");
114 // _safePop();
115 // push(opr);
116 }
117 }
118 public void quickUnboxIfNecessary(ClassNode type){
119 if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { // todo care when BigDecimal or BigIneteger on stack
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 { // numbers
126 cv.visitTypeInsn(CHECKCAST, "java/lang/Number");
127 cv.visitMethodInsn(INVOKEVIRTUAL, /*internName*/"java/lang/Number", type.getName() + "Value", "()" + getTypeDescription(type));
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 // lets avoid class loading
174 // return getType(name).getDescriptor();
175 if (name == null) {
176 return "Ljava/lang/Object;";
177 }
178 if (name.equals("void")) {
179 return "V";
180 }
181
182 if (name.startsWith("[")) { // todo need to take care of multi-dimentional array
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 // lets avoid class loading
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 //pushConstant(n.intValue());
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 // load nothing here for void
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); // todo? shall xload based on the type?
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 //int tempIndex = visitASTOREInTemp("reference", type);
533 cv.visitVarInsn(ALOAD, index);
534 cv.visitInsn(SWAP); // assuming the value on stack is single word
535 //cv.visitVarInsn(ALOAD, tempIndex);
536 cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V");
537 }
538 else {
539 store(variable.deriveBoxedVersion()); // todo br seems right hand values on the stack are always object refs, primitives boxed
540 // if (!varStored) {
541 // //visitVariableStartLabel(variable);
542 // varStored = true;
543 // }
544 }
545 }
546
547 // private int visitASTOREInTemp(String name, String type) {
548 // Variable var = defineVariable(createVariableName(name), type, false);
549 // int varIdx = var.getIndex();
550 // cv.visitVarInsn(ASTORE, varIdx);
551 // if (CREATE_DEBUG_INFO) cv.visitLabel(var.getStartLabel());
552 // return varIdx;
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("[]")) { // todo need process multi
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 }