1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.expression.spel;
18
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Stack;
24
25 import org.springframework.asm.ClassWriter;
26 import org.springframework.asm.MethodVisitor;
27 import org.springframework.asm.Opcodes;
28 import org.springframework.util.Assert;
29
30
31
32
33
34
35
36
37
38 public class CodeFlow implements Opcodes {
39
40
41
42
43
44
45
46 private final Stack<ArrayList<String>> compilationScopes;
47
48
49
50
51 private ClassWriter cw;
52
53
54
55
56
57
58 private List<FieldAdder> fieldAdders = null;
59
60
61
62
63
64
65
66 private List<ClinitAdder> clinitAdders = null;
67
68
69
70
71
72 private String clazzName;
73
74
75
76
77
78 private int nextFieldId = 1;
79
80
81
82
83
84 private int nextFreeVariableId = 1;
85
86 public CodeFlow(String clazzName, ClassWriter cw) {
87 this.compilationScopes = new Stack<ArrayList<String>>();
88 this.compilationScopes.add(new ArrayList<String>());
89 this.cw = cw;
90 this.clazzName = clazzName;
91 }
92
93
94
95
96
97
98 public void loadTarget(MethodVisitor mv) {
99 mv.visitVarInsn(ALOAD, 1);
100 }
101
102
103
104
105
106 public void pushDescriptor(String descriptor) {
107 Assert.notNull(descriptor, "Descriptor must not be null");
108 this.compilationScopes.peek().add(descriptor);
109 }
110
111
112
113
114
115
116 public void enterCompilationScope() {
117 this.compilationScopes.push(new ArrayList<String>());
118 }
119
120
121
122
123
124
125 public void exitCompilationScope() {
126 this.compilationScopes.pop();
127 }
128
129
130
131
132 public String lastDescriptor() {
133 if (this.compilationScopes.peek().isEmpty()) {
134 return null;
135 }
136 return this.compilationScopes.peek().get(this.compilationScopes.peek().size() - 1);
137 }
138
139
140
141
142
143
144 public void unboxBooleanIfNecessary(MethodVisitor mv) {
145 if (lastDescriptor().equals("Ljava/lang/Boolean")) {
146 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
147 }
148 }
149
150
151
152
153
154
155
156
157 public static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) {
158 switch (ch) {
159 case 'Z':
160 if (!stackDescriptor.equals("Ljava/lang/Boolean")) {
161 mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
162 }
163 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
164 break;
165 case 'B':
166 if (!stackDescriptor.equals("Ljava/lang/Byte")) {
167 mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
168 }
169 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
170 break;
171 case 'C':
172 if (!stackDescriptor.equals("Ljava/lang/Character")) {
173 mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
174 }
175 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
176 break;
177 case 'D':
178 if (!stackDescriptor.equals("Ljava/lang/Double")) {
179 mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
180 }
181 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
182 break;
183 case 'F':
184 if (!stackDescriptor.equals("Ljava/lang/Float")) {
185 mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
186 }
187 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
188 break;
189 case 'I':
190 if (!stackDescriptor.equals("Ljava/lang/Integer")) {
191 mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
192 }
193 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
194 break;
195 case 'J':
196 if (!stackDescriptor.equals("Ljava/lang/Long")) {
197 mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
198 }
199 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
200 break;
201 case 'S':
202 if (!stackDescriptor.equals("Ljava/lang/Short")) {
203 mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
204 }
205 mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
206 break;
207 default:
208 throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
209 }
210 }
211
212
213
214
215
216
217
218
219
220
221 public static String createSignatureDescriptor(Method method) {
222 Class<?>[] params = method.getParameterTypes();
223 StringBuilder sb = new StringBuilder();
224 sb.append("(");
225 for (Class<?> param : params) {
226 sb.append(toJvmDescriptor(param));
227 }
228 sb.append(")");
229 sb.append(toJvmDescriptor(method.getReturnType()));
230 return sb.toString();
231 }
232
233
234
235
236
237
238
239
240
241 public static String createSignatureDescriptor(Constructor<?> ctor) {
242 Class<?>[] params = ctor.getParameterTypes();
243 StringBuilder sb = new StringBuilder();
244 sb.append("(");
245 for (Class<?> param : params) {
246 sb.append(toJvmDescriptor(param));
247 }
248 sb.append(")V");
249 return sb.toString();
250 }
251
252
253
254
255
256
257
258
259
260 public static String toJvmDescriptor(Class<?> clazz) {
261 StringBuilder sb = new StringBuilder();
262 if (clazz.isArray()) {
263 while (clazz.isArray()) {
264 sb.append("[");
265 clazz = clazz.getComponentType();
266 }
267 }
268 if (clazz.isPrimitive()) {
269 if (clazz == Void.TYPE) {
270 sb.append('V');
271 }
272 else if (clazz == Integer.TYPE) {
273 sb.append('I');
274 }
275 else if (clazz == Boolean.TYPE) {
276 sb.append('Z');
277 }
278 else if (clazz == Character.TYPE) {
279 sb.append('C');
280 }
281 else if (clazz == Long.TYPE) {
282 sb.append('J');
283 }
284 else if (clazz == Double.TYPE) {
285 sb.append('D');
286 }
287 else if (clazz == Float.TYPE) {
288 sb.append('F');
289 }
290 else if (clazz == Byte.TYPE) {
291 sb.append('B');
292 }
293 else if (clazz == Short.TYPE) {
294 sb.append('S');
295 }
296 }
297 else {
298 sb.append("L");
299 sb.append(clazz.getName().replace('.', '/'));
300 sb.append(";");
301 }
302 return sb.toString();
303 }
304
305
306
307
308
309
310
311 public static String toDescriptorFromObject(Object value) {
312 if (value == null) {
313 return "Ljava/lang/Object";
314 }
315 else {
316 return toDescriptor(value.getClass());
317 }
318 }
319
320
321
322
323
324 public static boolean isBooleanCompatible(String descriptor) {
325 return (descriptor != null && (descriptor.equals("Z") || descriptor.equals("Ljava/lang/Boolean")));
326 }
327
328
329
330
331
332 public static boolean isPrimitive(String descriptor) {
333 return (descriptor != null && descriptor.length() == 1);
334 }
335
336
337
338
339
340 public static boolean isPrimitiveArray(String descriptor) {
341 boolean primitive = true;
342 for (int i = 0, max = descriptor.length(); i < max; i++) {
343 char ch = descriptor.charAt(i);
344 if (ch == '[') {
345 continue;
346 }
347 primitive = (ch != 'L');
348 break;
349 }
350 return primitive;
351 }
352
353
354
355
356
357
358 public static boolean areBoxingCompatible(String desc1, String desc2) {
359 if (desc1.equals(desc2)) {
360 return true;
361 }
362 if (desc1.length() == 1) {
363 if (desc1.equals("Z")) {
364 return desc2.equals("Ljava/lang/Boolean");
365 }
366 else if (desc1.equals("D")) {
367 return desc2.equals("Ljava/lang/Double");
368 }
369 else if (desc1.equals("F")) {
370 return desc2.equals("Ljava/lang/Float");
371 }
372 else if (desc1.equals("I")) {
373 return desc2.equals("Ljava/lang/Integer");
374 }
375 else if (desc1.equals("J")) {
376 return desc2.equals("Ljava/lang/Long");
377 }
378 }
379 else if (desc2.length() == 1) {
380 if (desc2.equals("Z")) {
381 return desc1.equals("Ljava/lang/Boolean");
382 }
383 else if (desc2.equals("D")) {
384 return desc1.equals("Ljava/lang/Double");
385 }
386 else if (desc2.equals("F")) {
387 return desc1.equals("Ljava/lang/Float");
388 }
389 else if (desc2.equals("I")) {
390 return desc1.equals("Ljava/lang/Integer");
391 }
392 else if (desc2.equals("J")) {
393 return desc1.equals("Ljava/lang/Long");
394 }
395 }
396 return false;
397 }
398
399
400
401
402
403
404
405
406 public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(String descriptor) {
407 if (descriptor == null) {
408 return false;
409 }
410 if (isPrimitiveOrUnboxableSupportedNumber(descriptor)) {
411 return true;
412 }
413 return ("Z".equals(descriptor) || descriptor.equals("Ljava/lang/Boolean"));
414 }
415
416
417
418
419
420
421
422
423 public static boolean isPrimitiveOrUnboxableSupportedNumber(String descriptor) {
424 if (descriptor == null) {
425 return false;
426 }
427 if (descriptor.length() == 1) {
428 return "DFIJ".contains(descriptor);
429 }
430 if (descriptor.startsWith("Ljava/lang/")) {
431 String name = descriptor.substring("Ljava/lang/".length());
432 if (name.equals("Double") || name.equals("Float") || name.equals("Integer") || name.equals("Long")) {
433 return true;
434 }
435 }
436 return false;
437 }
438
439
440
441
442
443
444
445 public static boolean isIntegerForNumericOp(Number number) {
446 return (number instanceof Integer || number instanceof Short || number instanceof Byte);
447 }
448
449
450
451
452
453 public static char toPrimitiveTargetDesc(String descriptor) {
454 if (descriptor.length() == 1) {
455 return descriptor.charAt(0);
456 }
457 else if (descriptor.equals("Ljava/lang/Boolean")) {
458 return 'Z';
459 }
460 else if (descriptor.equals("Ljava/lang/Byte")) {
461 return 'B';
462 }
463 else if (descriptor.equals("Ljava/lang/Character")) {
464 return 'C';
465 }
466 else if (descriptor.equals("Ljava/lang/Double")) {
467 return 'D';
468 }
469 else if (descriptor.equals("Ljava/lang/Float")) {
470 return 'F';
471 }
472 else if (descriptor.equals("Ljava/lang/Integer")) {
473 return 'I';
474 }
475 else if (descriptor.equals("Ljava/lang/Long")) {
476 return 'J';
477 }
478 else if (descriptor.equals("Ljava/lang/Short")) {
479 return 'S';
480 }
481 else {
482 throw new IllegalStateException("No primitive for '" + descriptor + "'");
483 }
484 }
485
486
487
488
489
490
491 public static void insertCheckCast(MethodVisitor mv, String descriptor) {
492 if (descriptor.length() != 1) {
493 if (descriptor.charAt(0) == '[') {
494 if (isPrimitiveArray(descriptor)) {
495 mv.visitTypeInsn(CHECKCAST, descriptor);
496 }
497 else {
498 mv.visitTypeInsn(CHECKCAST, descriptor + ";");
499 }
500 }
501 else {
502 if (!descriptor.equals("Ljava/lang/Object")) {
503
504 mv.visitTypeInsn(CHECKCAST, descriptor.substring(1));
505 }
506 }
507 }
508 }
509
510
511
512
513
514
515
516 public static void insertBoxIfNecessary(MethodVisitor mv, String descriptor) {
517 if (descriptor.length() == 1) {
518 insertBoxIfNecessary(mv, descriptor.charAt(0));
519 }
520 }
521
522
523
524
525
526
527
528 public static void insertBoxIfNecessary(MethodVisitor mv, char ch) {
529 switch (ch) {
530 case 'Z':
531 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
532 break;
533 case 'B':
534 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
535 break;
536 case 'C':
537 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
538 break;
539 case 'D':
540 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
541 break;
542 case 'F':
543 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
544 break;
545 case 'I':
546 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
547 break;
548 case 'J':
549 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
550 break;
551 case 'S':
552 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
553 break;
554 case 'L':
555 case 'V':
556 case '[':
557
558 break;
559 default:
560 throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
561 }
562 }
563
564
565
566
567
568
569
570
571 public static String toDescriptor(Class<?> type) {
572 String name = type.getName();
573 if (type.isPrimitive()) {
574 switch (name.length()) {
575 case 3:
576 return "I";
577 case 4:
578 if (name.equals("byte")) {
579 return "B";
580 }
581 else if (name.equals("char")) {
582 return "C";
583 }
584 else if (name.equals("long")) {
585 return "J";
586 }
587 else if (name.equals("void")) {
588 return "V";
589 }
590 break;
591 case 5:
592 if (name.equals("float")) {
593 return "F";
594 }
595 else if (name.equals("short")) {
596 return "S";
597 }
598 break;
599 case 6:
600 if (name.equals("double")) {
601 return "D";
602 }
603 break;
604 case 7:
605 if (name.equals("boolean")) {
606 return "Z";
607 }
608 break;
609 }
610 }
611 else {
612 if (name.charAt(0) != '[') {
613 return "L" + type.getName().replace('.', '/');
614 }
615 else {
616 if (name.endsWith(";")) {
617 return name.substring(0, name.length() - 1).replace('.', '/');
618 }
619 else {
620 return name;
621 }
622 }
623 }
624 return null;
625 }
626
627
628
629
630
631
632
633 public static String[] toParamDescriptors(Method method) {
634 return toDescriptors(method.getParameterTypes());
635 }
636
637
638
639
640
641
642
643 public static String[] toParamDescriptors(Constructor<?> ctor) {
644 return toDescriptors(ctor.getParameterTypes());
645 }
646
647
648
649
650
651
652 public static String[] toDescriptors(Class<?>[] types) {
653 int typesCount = types.length;
654 String[] descriptors = new String[typesCount];
655 for (int p = 0; p < typesCount; p++) {
656 descriptors[p] = toDescriptor(types[p]);
657 }
658 return descriptors;
659 }
660
661
662
663
664
665
666 public void finish() {
667 if (fieldAdders != null) {
668 for (FieldAdder fieldAdder: fieldAdders) {
669 fieldAdder.generateField(cw,this);
670 }
671 }
672 if (clinitAdders != null) {
673 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "<clinit>", "()V", null, null);
674 mv.visitCode();
675 nextFreeVariableId = 0;
676 for (ClinitAdder clinitAdder: clinitAdders) {
677 clinitAdder.generateCode(mv, this);
678 }
679 mv.visitInsn(RETURN);
680 mv.visitMaxs(0,0);
681 mv.visitEnd();
682 }
683 }
684
685
686
687
688
689
690 public void registerNewField(FieldAdder fieldAdder) {
691 if (fieldAdders == null) {
692 fieldAdders = new ArrayList<FieldAdder>();
693 }
694 fieldAdders.add(fieldAdder);
695 }
696
697
698
699
700
701
702 public void registerNewClinit(ClinitAdder clinitAdder) {
703 if (clinitAdders == null) {
704 clinitAdders = new ArrayList<ClinitAdder>();
705 }
706 clinitAdders.add(clinitAdder);
707 }
708
709 public int nextFieldId() {
710 return nextFieldId++;
711 }
712
713 public int nextFreeVariableId() {
714 return nextFreeVariableId++;
715 }
716
717 public String getClassname() {
718 return clazzName;
719 }
720
721 public interface FieldAdder {
722 public void generateField(ClassWriter cw, CodeFlow codeflow);
723 }
724
725 public interface ClinitAdder {
726 public void generateCode(MethodVisitor mv, CodeFlow codeflow);
727 }
728
729
730
731
732
733
734 public static void insertOptimalLoad(MethodVisitor mv, int value) {
735 if (value < 6) {
736 mv.visitInsn(ICONST_0+value);
737 }
738 else if (value < Byte.MAX_VALUE) {
739 mv.visitIntInsn(BIPUSH, value);
740 }
741 else if (value < Short.MAX_VALUE) {
742 mv.visitIntInsn(SIPUSH, value);
743 }
744 else {
745 mv.visitLdcInsn(value);
746 }
747 }
748
749
750
751
752
753
754
755
756 public static void insertArrayStore(MethodVisitor mv, String arrayElementType) {
757 if (arrayElementType.length()==1) {
758 switch (arrayElementType.charAt(0)) {
759 case 'I': mv.visitInsn(IASTORE); break;
760 case 'J': mv.visitInsn(LASTORE); break;
761 case 'F': mv.visitInsn(FASTORE); break;
762 case 'D': mv.visitInsn(DASTORE); break;
763 case 'B': mv.visitInsn(BASTORE); break;
764 case 'C': mv.visitInsn(CASTORE); break;
765 case 'S': mv.visitInsn(SASTORE); break;
766 case 'Z': mv.visitInsn(BASTORE); break;
767 default:
768 throw new IllegalArgumentException("Unexpected arraytype "+arrayElementType.charAt(0));
769 }
770 }
771 else {
772 mv.visitInsn(AASTORE);
773 }
774 }
775
776
777
778
779
780
781 public static int arrayCodeFor(String arraytype) {
782 switch (arraytype.charAt(0)) {
783 case 'I': return T_INT;
784 case 'J': return T_LONG;
785 case 'F': return T_FLOAT;
786 case 'D': return T_DOUBLE;
787 case 'B': return T_BYTE;
788 case 'C': return T_CHAR;
789 case 'S': return T_SHORT;
790 case 'Z': return T_BOOLEAN;
791 default:
792 throw new IllegalArgumentException("Unexpected arraytype "+arraytype.charAt(0));
793 }
794 }
795
796
797
798
799 public static boolean isReferenceTypeArray(String arraytype) {
800 int length = arraytype.length();
801 for (int i=0;i<length;i++) {
802 char ch = arraytype.charAt(i);
803 if (ch == '[') continue;
804 return ch=='L';
805 }
806 return false;
807 }
808
809
810
811
812
813
814
815
816
817 public static void insertNewArrayCode(MethodVisitor mv, int size, String arraytype) {
818 insertOptimalLoad(mv, size);
819 if (arraytype.length() == 1) {
820 mv.visitIntInsn(NEWARRAY, CodeFlow.arrayCodeFor(arraytype));
821 }
822 else {
823 if (arraytype.charAt(0) == '[') {
824
825
826 if (CodeFlow.isReferenceTypeArray(arraytype)) {
827 mv.visitTypeInsn(ANEWARRAY, arraytype+";");
828 } else {
829 mv.visitTypeInsn(ANEWARRAY, arraytype);
830 }
831 }
832 else {
833 mv.visitTypeInsn(ANEWARRAY, arraytype.substring(1));
834 }
835 }
836 }
837
838
839 }