Skip to content

Commit d8160b3

Browse files
committed
Comprehensive revision of SpEL's bytecode generation and number handling (BigInteger support, doubleValue fallback)
Issue: SPR-9913
1 parent e58b33a commit d8160b3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1441
-1335
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java

Lines changed: 135 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ public void exitCompilationScope() {
8686
}
8787

8888
/**
89-
* @return the descriptor for the item currently on top of the stack (in the current scope)
89+
* Return the descriptor for the item currently on top of the stack (in the current scope).
9090
*/
9191
public String lastDescriptor() {
92-
if (this.compilationScopes.peek().size() == 0) {
92+
if (this.compilationScopes.peek().isEmpty()) {
9393
return null;
9494
}
9595
return this.compilationScopes.peek().get(this.compilationScopes.peek().size() - 1);
@@ -106,6 +106,7 @@ public void unboxBooleanIfNecessary(MethodVisitor mv) {
106106
}
107107
}
108108

109+
109110
/**
110111
* Insert any necessary cast and value call to convert from a boxed type to a
111112
* primitive value
@@ -115,56 +116,56 @@ public void unboxBooleanIfNecessary(MethodVisitor mv) {
115116
*/
116117
public static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) {
117118
switch (ch) {
118-
case 'I':
119-
if (!stackDescriptor.equals("Ljava/lang/Integer")) {
120-
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
121-
}
122-
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
123-
break;
124-
case 'Z':
125-
if (!stackDescriptor.equals("Ljava/lang/Boolean")) {
126-
mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
127-
}
128-
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
129-
break;
130-
case 'B':
131-
if (!stackDescriptor.equals("Ljava/lang/Byte")) {
132-
mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
133-
}
134-
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
135-
break;
136-
case 'C':
137-
if (!stackDescriptor.equals("Ljava/lang/Character")) {
138-
mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
139-
}
140-
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
141-
break;
142-
case 'D':
143-
if (!stackDescriptor.equals("Ljava/lang/Double")) {
144-
mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
145-
}
146-
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
147-
break;
148-
case 'S':
149-
if (!stackDescriptor.equals("Ljava/lang/Short")) {
150-
mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
151-
}
152-
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
153-
break;
154-
case 'F':
155-
if (!stackDescriptor.equals("Ljava/lang/Float")) {
156-
mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
157-
}
158-
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
159-
break;
160-
case 'J':
161-
if (!stackDescriptor.equals("Ljava/lang/Long")) {
162-
mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
163-
}
164-
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
165-
break;
166-
default:
167-
throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
119+
case 'Z':
120+
if (!stackDescriptor.equals("Ljava/lang/Boolean")) {
121+
mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
122+
}
123+
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
124+
break;
125+
case 'B':
126+
if (!stackDescriptor.equals("Ljava/lang/Byte")) {
127+
mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
128+
}
129+
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
130+
break;
131+
case 'C':
132+
if (!stackDescriptor.equals("Ljava/lang/Character")) {
133+
mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
134+
}
135+
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
136+
break;
137+
case 'D':
138+
if (!stackDescriptor.equals("Ljava/lang/Double")) {
139+
mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
140+
}
141+
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
142+
break;
143+
case 'F':
144+
if (!stackDescriptor.equals("Ljava/lang/Float")) {
145+
mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
146+
}
147+
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
148+
break;
149+
case 'I':
150+
if (!stackDescriptor.equals("Ljava/lang/Integer")) {
151+
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
152+
}
153+
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
154+
break;
155+
case 'J':
156+
if (!stackDescriptor.equals("Ljava/lang/Long")) {
157+
mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
158+
}
159+
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
160+
break;
161+
case 'S':
162+
if (!stackDescriptor.equals("Ljava/lang/Short")) {
163+
mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
164+
}
165+
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
166+
break;
167+
default:
168+
throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
168169
}
169170
}
170171

@@ -182,10 +183,10 @@ public static String createSignatureDescriptor(Method method) {
182183
StringBuilder sb = new StringBuilder();
183184
sb.append("(");
184185
for (Class<?> param : params) {
185-
sb.append(toJVMDescriptor(param));
186+
sb.append(toJvmDescriptor(param));
186187
}
187188
sb.append(")");
188-
sb.append(toJVMDescriptor(method.getReturnType()));
189+
sb.append(toJvmDescriptor(method.getReturnType()));
189190
return sb.toString();
190191
}
191192

@@ -202,7 +203,7 @@ public static String createSignatureDescriptor(Constructor<?> ctor) {
202203
StringBuilder sb = new StringBuilder();
203204
sb.append("(");
204205
for (Class<?> param : params) {
205-
sb.append(toJVMDescriptor(param));
206+
sb.append(toJvmDescriptor(param));
206207
}
207208
sb.append(")V");
208209
return sb.toString();
@@ -216,7 +217,7 @@ public static String createSignatureDescriptor(Constructor<?> ctor) {
216217
* @param clazz a class
217218
* @return the JVM descriptor for the class
218219
*/
219-
public static String toJVMDescriptor(Class<?> clazz) {
220+
public static String toJvmDescriptor(Class<?> clazz) {
220221
StringBuilder sb = new StringBuilder();
221222
if (clazz.isArray()) {
222223
while (clazz.isArray()) {
@@ -319,37 +320,37 @@ public static boolean areBoxingCompatible(String desc1, String desc2) {
319320
return true;
320321
}
321322
if (desc1.length() == 1) {
322-
if (desc1.equals("D")) {
323+
if (desc1.equals("Z")) {
324+
return desc2.equals("Ljava/lang/Boolean");
325+
}
326+
else if (desc1.equals("D")) {
323327
return desc2.equals("Ljava/lang/Double");
324328
}
325329
else if (desc1.equals("F")) {
326330
return desc2.equals("Ljava/lang/Float");
327331
}
328-
else if (desc1.equals("J")) {
329-
return desc2.equals("Ljava/lang/Long");
330-
}
331332
else if (desc1.equals("I")) {
332333
return desc2.equals("Ljava/lang/Integer");
333334
}
334-
else if (desc1.equals("Z")) {
335-
return desc2.equals("Ljava/lang/Boolean");
335+
else if (desc1.equals("J")) {
336+
return desc2.equals("Ljava/lang/Long");
336337
}
337338
}
338339
else if (desc2.length() == 1) {
339-
if (desc2.equals("D")) {
340+
if (desc2.equals("Z")) {
341+
return desc1.equals("Ljava/lang/Boolean");
342+
}
343+
else if (desc2.equals("D")) {
340344
return desc1.equals("Ljava/lang/Double");
341345
}
342346
else if (desc2.equals("F")) {
343347
return desc1.equals("Ljava/lang/Float");
344348
}
345-
else if (desc2.equals("J")) {
346-
return desc1.equals("Ljava/lang/Long");
347-
}
348349
else if (desc2.equals("I")) {
349350
return desc1.equals("Ljava/lang/Integer");
350351
}
351-
else if (desc2.equals("Z")) {
352-
return desc1.equals("Ljava/lang/Boolean");
352+
else if (desc2.equals("J")) {
353+
return desc1.equals("Ljava/lang/Long");
353354
}
354355
}
355356
return false;
@@ -366,17 +367,10 @@ public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(String desc
366367
if (descriptor == null) {
367368
return false;
368369
}
369-
if (descriptor.length( )== 1) {
370-
return ("DFJZI".indexOf(descriptor.charAt(0)) != -1);
371-
}
372-
if (descriptor.startsWith("Ljava/lang/")) {
373-
if (descriptor.equals("Ljava/lang/Double") || descriptor.equals("Ljava/lang/Integer") ||
374-
descriptor.equals("Ljava/lang/Float") || descriptor.equals("Ljava/lang/Long") ||
375-
descriptor.equals("Ljava/lang/Boolean")) {
376-
return true;
377-
}
370+
if (isPrimitiveOrUnboxableSupportedNumber(descriptor)) {
371+
return true;
378372
}
379-
return false;
373+
return ("Z".equals(descriptor) || descriptor.equals("Ljava/lang/Boolean"));
380374
}
381375

382376
/**
@@ -391,17 +385,27 @@ public static boolean isPrimitiveOrUnboxableSupportedNumber(String descriptor) {
391385
return false;
392386
}
393387
if (descriptor.length() == 1) {
394-
return ("DFJI".indexOf(descriptor.charAt(0)) != -1);
388+
return "DFIJ".contains(descriptor);
395389
}
396390
if (descriptor.startsWith("Ljava/lang/")) {
397-
if (descriptor.equals("Ljava/lang/Double") || descriptor.equals("Ljava/lang/Integer") ||
398-
descriptor.equals("Ljava/lang/Float") || descriptor.equals("Ljava/lang/Long")) {
391+
String name = descriptor.substring("Ljava/lang/".length());
392+
if (name.equals("Double") || name.equals("Float") || name.equals("Integer") || name.equals("Long")) {
399393
return true;
400394
}
401395
}
402396
return false;
403397
}
404398

399+
/**
400+
* Determine whether the given number is to be considered as an integer
401+
* for the purposes of a numeric operation at the bytecode level.
402+
* @param number the number to check
403+
* @return {@code true} if it is an {@link Integer}, {@link Short} or {@link Byte}
404+
*/
405+
public static boolean isIntegerForNumericOp(Number number) {
406+
return (number instanceof Integer || number instanceof Short || number instanceof Byte);
407+
}
408+
405409
/**
406410
* @param descriptor a descriptor for a type that should have a primitive representation
407411
* @return the single character descriptor for a primitive input descriptor
@@ -410,23 +414,32 @@ public static char toPrimitiveTargetDesc(String descriptor) {
410414
if (descriptor.length() == 1) {
411415
return descriptor.charAt(0);
412416
}
413-
if (descriptor.equals("Ljava/lang/Double")) {
414-
return 'D';
417+
else if (descriptor.equals("Ljava/lang/Boolean")) {
418+
return 'Z';
415419
}
416-
else if (descriptor.equals("Ljava/lang/Integer")) {
417-
return 'I';
420+
else if (descriptor.equals("Ljava/lang/Byte")) {
421+
return 'B';
422+
}
423+
else if (descriptor.equals("Ljava/lang/Character")) {
424+
return 'C';
425+
}
426+
else if (descriptor.equals("Ljava/lang/Double")) {
427+
return 'D';
418428
}
419429
else if (descriptor.equals("Ljava/lang/Float")) {
420430
return 'F';
421431
}
432+
else if (descriptor.equals("Ljava/lang/Integer")) {
433+
return 'I';
434+
}
422435
else if (descriptor.equals("Ljava/lang/Long")) {
423436
return 'J';
424437
}
425-
else if (descriptor.equals("Ljava/lang/Boolean")) {
426-
return 'Z';
438+
else if (descriptor.equals("Ljava/lang/Short")) {
439+
return 'S';
427440
}
428441
else {
429-
throw new IllegalStateException("No primitive for '"+descriptor+"'");
442+
throw new IllegalStateException("No primitive for '" + descriptor + "'");
430443
}
431444
}
432445

@@ -474,37 +487,37 @@ public static void insertBoxIfNecessary(MethodVisitor mv, String descriptor) {
474487
*/
475488
public static void insertBoxIfNecessary(MethodVisitor mv, char ch) {
476489
switch (ch) {
477-
case 'I':
478-
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
479-
break;
480-
case 'F':
481-
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
482-
break;
483-
case 'S':
484-
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
485-
break;
486-
case 'Z':
487-
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
488-
break;
489-
case 'J':
490-
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
491-
break;
492-
case 'D':
493-
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
494-
break;
495-
case 'C':
496-
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
497-
break;
498-
case 'B':
499-
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
500-
break;
501-
case 'L':
490+
case 'Z':
491+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
492+
break;
493+
case 'B':
494+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
495+
break;
496+
case 'C':
497+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
498+
break;
499+
case 'D':
500+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
501+
break;
502+
case 'F':
503+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
504+
break;
505+
case 'I':
506+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
507+
break;
508+
case 'J':
509+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
510+
break;
511+
case 'S':
512+
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
513+
break;
514+
case 'L':
502515
case 'V':
503-
case '[':
504-
// no box needed
505-
break;
506-
default:
507-
throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
516+
case '[':
517+
// no box needed
518+
break;
519+
default:
520+
throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
508521
}
509522
}
510523

@@ -522,14 +535,14 @@ public static String toDescriptor(Class<?> type) {
522535
case 3:
523536
return "I";
524537
case 4:
525-
if (name.equals("long")) {
526-
return "J";
538+
if (name.equals("byte")) {
539+
return "B";
527540
}
528541
else if (name.equals("char")) {
529542
return "C";
530543
}
531-
else if (name.equals("byte")) {
532-
return "B";
544+
else if (name.equals("long")) {
545+
return "J";
533546
}
534547
else if (name.equals("void")) {
535548
return "V";

spring-expression/src/main/java/org/springframework/expression/spel/CompilablePropertyAccessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public interface CompilablePropertyAccessor extends PropertyAccessor, Opcodes {
4545
* using context information from the codeflow where necessary.
4646
* @param propertyName the name of the property
4747
* @param mv the Asm method visitor into which code should be generated
48-
* @param codeflow the current state of the expression compiler
48+
* @param cf the current state of the expression compiler
4949
*/
50-
void generateCode(String propertyName, MethodVisitor mv, CodeFlow codeflow);
50+
void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf);
5151

5252
}

0 commit comments

Comments
 (0)