21
21
import java .util .List ;
22
22
import java .util .Map ;
23
23
24
+ import org .springframework .asm .Label ;
24
25
import org .springframework .asm .MethodVisitor ;
25
26
import org .springframework .core .convert .TypeDescriptor ;
26
27
import org .springframework .expression .AccessException ;
@@ -47,6 +48,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
47
48
48
49
private final boolean nullSafe ;
49
50
51
+ private String originalPrimitiveExitTypeDescriptor = null ;
52
+
50
53
private final String name ;
51
54
52
55
private volatile PropertyAccessor cachedReadAccessor ;
@@ -83,7 +86,7 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
83
86
PropertyAccessor accessorToUse = this .cachedReadAccessor ;
84
87
if (accessorToUse instanceof CompilablePropertyAccessor ) {
85
88
CompilablePropertyAccessor accessor = (CompilablePropertyAccessor ) accessorToUse ;
86
- this . exitTypeDescriptor = CodeFlow .toDescriptor (accessor .getPropertyType ());
89
+ setExitTypeDescriptor ( CodeFlow .toDescriptor (accessor .getPropertyType () ));
87
90
}
88
91
return tv ;
89
92
}
@@ -350,8 +353,40 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) {
350
353
if (!(accessorToUse instanceof CompilablePropertyAccessor )) {
351
354
throw new IllegalStateException ("Property accessor is not compilable: " + accessorToUse );
352
355
}
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
+ }
353
366
((CompilablePropertyAccessor ) accessorToUse ).generateCode (this .name , mv , cf );
354
367
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
+ }
355
390
}
356
391
357
392
@@ -379,8 +414,7 @@ public TypedValue getValue() {
379
414
this .ref .getValueInternal (this .contextObject , this .evalContext , this .autoGrowNullReferences );
380
415
PropertyAccessor accessorToUse = this .ref .cachedReadAccessor ;
381
416
if (accessorToUse instanceof CompilablePropertyAccessor ) {
382
- this .ref .exitTypeDescriptor =
383
- CodeFlow .toDescriptor (((CompilablePropertyAccessor ) accessorToUse ).getPropertyType ());
417
+ this .ref .setExitTypeDescriptor (CodeFlow .toDescriptor (((CompilablePropertyAccessor ) accessorToUse ).getPropertyType ()));
384
418
}
385
419
return value ;
386
420
}
0 commit comments