Skip to content

Commit d3acf45

Browse files
committed
Modify SpEL code gen to take account of null safe refs
With this change the code generation for method and property references is modified to include branching logic in the case of null safe dereferencing (?.). This is complicated by the possible usage of primitives on the left hand side of the dereference. To cope with this case primitives are promoted to boxed types when this situation occurs enabling null to be passed as a possible result. Issue: SPR-16489
1 parent 573f1d7 commit d3acf45

File tree

4 files changed

+284
-15
lines changed

4 files changed

+284
-15
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,4 +1003,19 @@ public interface ClinitAdder {
10031003
void generateCode(MethodVisitor mv, CodeFlow codeflow);
10041004
}
10051005

1006+
public static String toBoxedDescriptor(String primitiveDescriptor) {
1007+
switch (primitiveDescriptor.charAt(0)) {
1008+
case 'I': return "Ljava/lang/Integer";
1009+
case 'J': return "Ljava/lang/Long";
1010+
case 'F': return "Ljava/lang/Float";
1011+
case 'D': return "Ljava/lang/Double";
1012+
case 'B': return "Ljava/lang/Byte";
1013+
case 'C': return "Ljava/lang/Character";
1014+
case 'S': return "Ljava/lang/Short";
1015+
case 'Z': return "Ljava/lang/Boolean";
1016+
default:
1017+
throw new IllegalArgumentException("Unexpected non primitive descriptor "+primitiveDescriptor);
1018+
}
1019+
}
1020+
10061021
}

spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Collections;
2525
import java.util.List;
2626

27+
import org.springframework.asm.Label;
2728
import org.springframework.asm.MethodVisitor;
2829
import org.springframework.core.convert.TypeDescriptor;
2930
import org.springframework.expression.AccessException;
@@ -53,6 +54,8 @@ public class MethodReference extends SpelNodeImpl {
5354

5455
private final boolean nullSafe;
5556

57+
private String originalPrimitiveExitTypeDescriptor = null;
58+
5659
private volatile CachedMethodExecutor cachedExecutor;
5760

5861

@@ -233,7 +236,14 @@ private void updateExitTypeDescriptor() {
233236
CachedMethodExecutor executorToCheck = this.cachedExecutor;
234237
if (executorToCheck != null && executorToCheck.get() instanceof ReflectiveMethodExecutor) {
235238
Method method = ((ReflectiveMethodExecutor) executorToCheck.get()).getMethod();
236-
this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
239+
String descriptor = CodeFlow.toDescriptor(method.getReturnType());
240+
if (this.nullSafe && CodeFlow.isPrimitive(descriptor)) {
241+
originalPrimitiveExitTypeDescriptor = descriptor;
242+
this.exitTypeDescriptor = CodeFlow.toBoxedDescriptor(descriptor);
243+
}
244+
else {
245+
this.exitTypeDescriptor = descriptor;
246+
}
237247
}
238248
}
239249

@@ -293,17 +303,23 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) {
293303
boolean isStaticMethod = Modifier.isStatic(method.getModifiers());
294304
String descriptor = cf.lastDescriptor();
295305

296-
if (descriptor == null) {
297-
if (!isStaticMethod) {
298-
// Nothing on the stack but something is needed
299-
cf.loadTarget(mv);
300-
}
306+
Label skipIfNull = null;
307+
if (descriptor == null && !isStaticMethod) {
308+
// Nothing on the stack but something is needed
309+
cf.loadTarget(mv);
301310
}
302-
else {
303-
if (isStaticMethod) {
304-
// Something on the stack when nothing is needed
305-
mv.visitInsn(POP);
306-
}
311+
if ((descriptor != null || !isStaticMethod) && nullSafe) {
312+
mv.visitInsn(DUP);
313+
skipIfNull = new Label();
314+
Label continueLabel = new Label();
315+
mv.visitJumpInsn(IFNONNULL,continueLabel);
316+
CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor);
317+
mv.visitJumpInsn(GOTO, skipIfNull);
318+
mv.visitLabel(continueLabel);
319+
}
320+
if (descriptor != null && isStaticMethod) {
321+
// Something on the stack when nothing is needed
322+
mv.visitInsn(POP);
307323
}
308324

309325
if (CodeFlow.isPrimitive(descriptor)) {
@@ -323,6 +339,14 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) {
323339
mv.visitMethodInsn((isStaticMethod ? INVOKESTATIC : INVOKEVIRTUAL), classDesc, method.getName(),
324340
CodeFlow.createSignatureDescriptor(method), method.getDeclaringClass().isInterface());
325341
cf.pushDescriptor(this.exitTypeDescriptor);
342+
if (originalPrimitiveExitTypeDescriptor != null) {
343+
// The output of the accessor will be a primitive but from the block above it might be null,
344+
// so to have a 'common stack' element at skipIfNull target we need to box the primitive
345+
CodeFlow.insertBoxIfNecessary(mv, originalPrimitiveExitTypeDescriptor);
346+
}
347+
if (skipIfNull != null) {
348+
mv.visitLabel(skipIfNull);
349+
}
326350
}
327351

328352

spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.List;
2222
import java.util.Map;
2323

24+
import org.springframework.asm.Label;
2425
import org.springframework.asm.MethodVisitor;
2526
import org.springframework.core.convert.TypeDescriptor;
2627
import org.springframework.expression.AccessException;
@@ -47,6 +48,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
4748

4849
private final boolean nullSafe;
4950

51+
private String originalPrimitiveExitTypeDescriptor = null;
52+
5053
private final String name;
5154

5255
private volatile PropertyAccessor cachedReadAccessor;
@@ -83,7 +86,7 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
8386
PropertyAccessor accessorToUse = this.cachedReadAccessor;
8487
if (accessorToUse instanceof CompilablePropertyAccessor) {
8588
CompilablePropertyAccessor accessor = (CompilablePropertyAccessor) accessorToUse;
86-
this.exitTypeDescriptor = CodeFlow.toDescriptor(accessor.getPropertyType());
89+
setExitTypeDescriptor(CodeFlow.toDescriptor(accessor.getPropertyType()));
8790
}
8891
return tv;
8992
}
@@ -350,8 +353,40 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) {
350353
if (!(accessorToUse instanceof CompilablePropertyAccessor)) {
351354
throw new IllegalStateException("Property accessor is not compilable: " + accessorToUse);
352355
}
356+
Label skipIfNull = null;
357+
if (nullSafe) {
358+
mv.visitInsn(DUP);
359+
skipIfNull = new Label();
360+
Label continueLabel = new Label();
361+
mv.visitJumpInsn(IFNONNULL,continueLabel);
362+
CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor);
363+
mv.visitJumpInsn(GOTO, skipIfNull);
364+
mv.visitLabel(continueLabel);
365+
}
353366
((CompilablePropertyAccessor) accessorToUse).generateCode(this.name, mv, cf);
354367
cf.pushDescriptor(this.exitTypeDescriptor);
368+
if (originalPrimitiveExitTypeDescriptor != null) {
369+
// The output of the accessor is a primitive but from the block above it might be null,
370+
// so to have a common stack element type at skipIfNull target it is necessary
371+
// to box the primitive
372+
CodeFlow.insertBoxIfNecessary(mv, originalPrimitiveExitTypeDescriptor);
373+
}
374+
if (skipIfNull != null) {
375+
mv.visitLabel(skipIfNull);
376+
}
377+
}
378+
379+
void setExitTypeDescriptor(String descriptor) {
380+
// If this property or field access would return a primitive - and yet
381+
// it is also marked null safe - then the exit type descriptor must be
382+
// promoted to the box type to allow a null value to be passed on
383+
if (this.nullSafe && CodeFlow.isPrimitive(descriptor)) {
384+
this.originalPrimitiveExitTypeDescriptor = descriptor;
385+
this.exitTypeDescriptor = CodeFlow.toBoxedDescriptor(descriptor);
386+
}
387+
else {
388+
this.exitTypeDescriptor = descriptor;
389+
}
355390
}
356391

357392

@@ -379,8 +414,7 @@ public TypedValue getValue() {
379414
this.ref.getValueInternal(this.contextObject, this.evalContext, this.autoGrowNullReferences);
380415
PropertyAccessor accessorToUse = this.ref.cachedReadAccessor;
381416
if (accessorToUse instanceof CompilablePropertyAccessor) {
382-
this.ref.exitTypeDescriptor =
383-
CodeFlow.toDescriptor(((CompilablePropertyAccessor) accessorToUse).getPropertyType());
417+
this.ref.setExitTypeDescriptor(CodeFlow.toDescriptor(((CompilablePropertyAccessor) accessorToUse).getPropertyType()));
384418
}
385419
return value;
386420
}

0 commit comments

Comments
 (0)