Skip to content

Commit b64d752

Browse files
aclementjhoeller
authored andcommitted
Fix SpEL compilation of static method/property/field operations
Before this change the compilation of a method reference or property/field access was not properly cleaning up the stack if compilation meant calling a static method or accessing a static field. In these cases there is no need for a target object on the stack and it should be removed if present. For a simple expression it is harmless since the end result of the expression is the thing on the top of the stack, but for nested expressions if the inner expression suffered this issue, the outer expression can find itself operating on the wrong element. The particular issue covered the case of a static field access but this fix (and associated tests) cover static method, property and field access. Issue: SPR-13781 (cherry picked from commit a28fc76)
1 parent 1d0c2f6 commit b64d752

File tree

4 files changed

+199
-72
lines changed

4 files changed

+199
-72
lines changed

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
6262
}
6363
try {
6464
state.pushActiveContextObject(result);
65-
nextNode = this.children[cc-1];
65+
nextNode = this.children[cc - 1];
6666
return nextNode.getValueRef(state);
6767
}
6868
finally {
@@ -124,14 +124,8 @@ public boolean isCompilable() {
124124

125125
@Override
126126
public void generateCode(MethodVisitor mv, CodeFlow cf) {
127-
// TODO could optimize T(SomeType).staticMethod - no need to generate the T() part
128127
for (int i = 0; i < this.children.length;i++) {
129-
SpelNodeImpl child = this.children[i];
130-
if (child instanceof TypeReference && (i + 1) < this.children.length &&
131-
this.children[i + 1] instanceof MethodReference) {
132-
continue;
133-
}
134-
child.generateCode(mv, cf);
128+
this.children[i].generateCode(mv, cf);
135129
}
136130
cf.pushDescriptor(this.exitTypeDescriptor);
137131
}

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

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -287,35 +287,40 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) {
287287
throw new IllegalStateException("No applicable cached executor found: " + executorToCheck);
288288
}
289289

290-
ReflectiveMethodExecutor methodExecutor = (ReflectiveMethodExecutor)executorToCheck.get();
290+
ReflectiveMethodExecutor methodExecutor = (ReflectiveMethodExecutor) executorToCheck.get();
291291
Method method = methodExecutor.getMethod();
292292
boolean isStaticMethod = Modifier.isStatic(method.getModifiers());
293293
String descriptor = cf.lastDescriptor();
294294

295-
if (descriptor == null && !isStaticMethod) {
296-
cf.loadTarget(mv);
295+
if (descriptor == null) {
296+
if (!isStaticMethod) {
297+
// Nothing on the stack but something is needed
298+
cf.loadTarget(mv);
299+
}
300+
}
301+
else {
302+
if (isStaticMethod) {
303+
// Something on the stack when nothing is needed
304+
mv.visitInsn(POP);
305+
}
297306
}
298307

299308
if (CodeFlow.isPrimitive(descriptor)) {
300309
CodeFlow.insertBoxIfNecessary(mv, descriptor.charAt(0));
301310
}
302311

303-
boolean itf = method.getDeclaringClass().isInterface();
304-
String methodDeclaringClassSlashedDescriptor = null;
305-
if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
306-
methodDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.', '/');
307-
}
308-
else {
309-
methodDeclaringClassSlashedDescriptor = methodExecutor.getPublicDeclaringClass().getName().replace('.', '/');
310-
}
312+
String classDesc = (Modifier.isPublic(method.getDeclaringClass().getModifiers()) ?
313+
method.getDeclaringClass().getName().replace('.', '/') :
314+
methodExecutor.getPublicDeclaringClass().getName().replace('.', '/'));
311315
if (!isStaticMethod) {
312-
if (descriptor == null || !descriptor.substring(1).equals(methodDeclaringClassSlashedDescriptor)) {
313-
CodeFlow.insertCheckCast(mv, "L"+ methodDeclaringClassSlashedDescriptor);
316+
if (descriptor == null || !descriptor.substring(1).equals(classDesc)) {
317+
CodeFlow.insertCheckCast(mv, "L" + classDesc);
314318
}
315319
}
316-
generateCodeForArguments(mv, cf, method, children);
317-
mv.visitMethodInsn(isStaticMethod ? INVOKESTATIC : INVOKEVIRTUAL,
318-
methodDeclaringClassSlashedDescriptor, method.getName(), CodeFlow.createSignatureDescriptor(method), itf);
320+
321+
generateCodeForArguments(mv, cf, method, this.children);
322+
mv.visitMethodInsn((isStaticMethod ? INVOKESTATIC : INVOKEVIRTUAL), classDesc, method.getName(),
323+
CodeFlow.createSignatureDescriptor(method), method.getDeclaringClass().isInterface());
319324
cf.pushDescriptor(this.exitTypeDescriptor);
320325
}
321326

spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java

Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -58,16 +58,17 @@
5858
*/
5959
public class ReflectivePropertyAccessor implements PropertyAccessor {
6060

61+
private static final Set<Class<?>> ANY_TYPES = Collections.emptySet();
62+
6163
private static final Set<Class<?>> BOOLEAN_TYPES;
64+
6265
static {
6366
Set<Class<?>> booleanTypes = new HashSet<Class<?>>();
6467
booleanTypes.add(Boolean.class);
6568
booleanTypes.add(Boolean.TYPE);
6669
BOOLEAN_TYPES = Collections.unmodifiableSet(booleanTypes);
6770
}
6871

69-
private static final Set<Class<?>> ANY_TYPES = Collections.emptySet();
70-
7172

7273
private final Map<CacheKey, InvokerPair> readerCache = new ConcurrentHashMap<CacheKey, InvokerPair>(64);
7374

@@ -120,9 +121,9 @@ public boolean canRead(EvaluationContext context, Object target, String name) th
120121
}
121122
return false;
122123
}
123-
124+
124125
public Member getLastReadInvokerPair() {
125-
return lastReadInvokerPair.member;
126+
return this.lastReadInvokerPair.member;
126127
}
127128

128129
@Override
@@ -165,7 +166,7 @@ public TypedValue read(EvaluationContext context, Object target, String name) th
165166
return new TypedValue(value, invoker.typeDescriptor.narrow(value));
166167
}
167168
catch (Exception ex) {
168-
throw new AccessException("Unable to access property '" + name + "' through getter", ex);
169+
throw new AccessException("Unable to access property '" + name + "' through getter method", ex);
169170
}
170171
}
171172
}
@@ -187,12 +188,12 @@ public TypedValue read(EvaluationContext context, Object target, String name) th
187188
return new TypedValue(value, invoker.typeDescriptor.narrow(value));
188189
}
189190
catch (Exception ex) {
190-
throw new AccessException("Unable to access field: " + name, ex);
191+
throw new AccessException("Unable to access field '" + name + "'", ex);
191192
}
192193
}
193194
}
194195

195-
throw new AccessException("Neither getter nor field found for property '" + name + "'");
196+
throw new AccessException("Neither getter method nor field found for property '" + name + "'");
196197
}
197198

198199
@Override
@@ -240,7 +241,7 @@ public void write(EvaluationContext context, Object target, String name, Object
240241
newValue, TypeDescriptor.forObject(newValue), typeDescriptor);
241242
}
242243
catch (EvaluationException evaluationException) {
243-
throw new AccessException("Type conversion failure",evaluationException);
244+
throw new AccessException("Type conversion failure", evaluationException);
244245
}
245246
}
246247
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
@@ -262,7 +263,7 @@ public void write(EvaluationContext context, Object target, String name, Object
262263
return;
263264
}
264265
catch (Exception ex) {
265-
throw new AccessException("Unable to access property '" + name + "' through setter", ex);
266+
throw new AccessException("Unable to access property '" + name + "' through setter method", ex);
266267
}
267268
}
268269
}
@@ -283,12 +284,12 @@ public void write(EvaluationContext context, Object target, String name, Object
283284
return;
284285
}
285286
catch (Exception ex) {
286-
throw new AccessException("Unable to access field: " + name, ex);
287+
throw new AccessException("Unable to access field '" + name + "'", ex);
287288
}
288289
}
289290
}
290291

291-
throw new AccessException("Neither setter nor field found for property '" + name + "'");
292+
throw new AccessException("Neither setter method nor field found for property '" + name + "'");
292293
}
293294

294295
private TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
@@ -469,11 +470,11 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext evalContext, Obj
469470
InvokerPair invocationTarget = this.readerCache.get(cacheKey);
470471

471472
if (invocationTarget == null || invocationTarget.member instanceof Method) {
472-
Method method = (Method) (invocationTarget==null?null:invocationTarget.member);
473+
Method method = (Method) (invocationTarget != null ? invocationTarget.member : null);
473474
if (method == null) {
474475
method = findGetterForProperty(name, type, target);
475476
if (method != null) {
476-
invocationTarget = new InvokerPair(method,new TypeDescriptor(new MethodParameter(method,-1)));
477+
invocationTarget = new InvokerPair(method, new TypeDescriptor(new MethodParameter(method, -1)));
477478
ReflectionUtils.makeAccessible(method);
478479
this.readerCache.put(cacheKey, invocationTarget);
479480
}
@@ -497,6 +498,7 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext evalContext, Obj
497498
return new OptimalPropertyAccessor(invocationTarget);
498499
}
499500
}
501+
500502
return this;
501503
}
502504

@@ -577,16 +579,8 @@ public static class OptimalPropertyAccessor implements CompilablePropertyAccesso
577579
OptimalPropertyAccessor(InvokerPair target) {
578580
this.member = target.member;
579581
this.typeDescriptor = target.typeDescriptor;
580-
if (this.member instanceof Field) {
581-
Field field = (Field) this.member;
582-
this.needsToBeMadeAccessible = (!Modifier.isPublic(field.getModifiers()) ||
583-
!Modifier.isPublic(field.getDeclaringClass().getModifiers())) && !field.isAccessible();
584-
}
585-
else {
586-
Method method = (Method) this.member;
587-
this.needsToBeMadeAccessible = ((!Modifier.isPublic(method.getModifiers()) ||
588-
!Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible());
589-
}
582+
this.needsToBeMadeAccessible = (!Modifier.isPublic(this.member.getModifiers()) ||
583+
!Modifier.isPublic(this.member.getDeclaringClass().getModifiers()));
590584
}
591585

592586
@Override
@@ -599,10 +593,12 @@ public boolean canRead(EvaluationContext context, Object target, String name) th
599593
if (target == null) {
600594
return false;
601595
}
596+
602597
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
603598
if (type.isArray()) {
604599
return false;
605600
}
601+
606602
if (this.member instanceof Method) {
607603
Method method = (Method) this.member;
608604
String getterName = "get" + StringUtils.capitalize(name);
@@ -621,30 +617,31 @@ public boolean canRead(EvaluationContext context, Object target, String name) th
621617
@Override
622618
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
623619
if (this.member instanceof Method) {
620+
Method method = (Method) this.member;
624621
try {
625-
if (this.needsToBeMadeAccessible) {
626-
ReflectionUtils.makeAccessible((Method) this.member);
622+
if (this.needsToBeMadeAccessible && !method.isAccessible()) {
623+
method.setAccessible(true);
627624
}
628-
Object value = ((Method) this.member).invoke(target);
625+
Object value = method.invoke(target);
629626
return new TypedValue(value, this.typeDescriptor.narrow(value));
630627
}
631628
catch (Exception ex) {
632-
throw new AccessException("Unable to access property '" + name + "' through getter", ex);
629+
throw new AccessException("Unable to access property '" + name + "' through getter method", ex);
633630
}
634631
}
635-
if (this.member instanceof Field) {
632+
else {
633+
Field field = (Field) this.member;
636634
try {
637-
if (this.needsToBeMadeAccessible) {
638-
ReflectionUtils.makeAccessible((Field) this.member);
635+
if (this.needsToBeMadeAccessible && !field.isAccessible()) {
636+
field.setAccessible(true);
639637
}
640-
Object value = ((Field) this.member).get(target);
638+
Object value = field.get(target);
641639
return new TypedValue(value, this.typeDescriptor.narrow(value));
642640
}
643641
catch (Exception ex) {
644-
throw new AccessException("Unable to access field: " + name, ex);
642+
throw new AccessException("Unable to access field '" + name + "'", ex);
645643
}
646644
}
647-
throw new AccessException("Neither getter nor field found for property '" + name + "'");
648645
}
649646

650647
@Override
@@ -656,7 +653,7 @@ public boolean canWrite(EvaluationContext context, Object target, String name) {
656653
public void write(EvaluationContext context, Object target, String name, Object newValue) {
657654
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
658655
}
659-
656+
660657
@Override
661658
public boolean isCompilable() {
662659
return (Modifier.isPublic(this.member.getModifiers()) &&
@@ -665,34 +662,43 @@ public boolean isCompilable() {
665662

666663
@Override
667664
public Class<?> getPropertyType() {
668-
if (this.member instanceof Field) {
669-
return ((Field) this.member).getType();
665+
if (this.member instanceof Method) {
666+
return ((Method) this.member).getReturnType();
670667
}
671668
else {
672-
return ((Method) this.member).getReturnType();
669+
return ((Field) this.member).getType();
673670
}
674671
}
675672

676673
@Override
677674
public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
678675
boolean isStatic = Modifier.isStatic(this.member.getModifiers());
679676
String descriptor = cf.lastDescriptor();
680-
String memberDeclaringClassSlashedDescriptor = this.member.getDeclaringClass().getName().replace('.', '/');
677+
String classDesc = this.member.getDeclaringClass().getName().replace('.', '/');
678+
681679
if (!isStatic) {
682680
if (descriptor == null) {
683681
cf.loadTarget(mv);
684682
}
685-
if (descriptor == null || !memberDeclaringClassSlashedDescriptor.equals(descriptor.substring(1))) {
686-
mv.visitTypeInsn(CHECKCAST, memberDeclaringClassSlashedDescriptor);
683+
if (descriptor == null || !classDesc.equals(descriptor.substring(1))) {
684+
mv.visitTypeInsn(CHECKCAST, classDesc);
687685
}
688686
}
689-
if (this.member instanceof Field) {
690-
mv.visitFieldInsn(isStatic ? GETSTATIC : GETFIELD, memberDeclaringClassSlashedDescriptor,
691-
this.member.getName(), CodeFlow.toJvmDescriptor(((Field) this.member).getType()));
687+
else {
688+
if (descriptor != null) {
689+
// A static field/method call will not consume what is on the stack,
690+
// it needs to be popped off.
691+
mv.visitInsn(POP);
692+
}
693+
}
694+
695+
if (this.member instanceof Method) {
696+
mv.visitMethodInsn((isStatic ? INVOKESTATIC : INVOKEVIRTUAL), classDesc, this.member.getName(),
697+
CodeFlow.createSignatureDescriptor((Method) this.member), false);
692698
}
693699
else {
694-
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor,
695-
this.member.getName(), CodeFlow.createSignatureDescriptor((Method) this.member),false);
700+
mv.visitFieldInsn((isStatic ? GETSTATIC : GETFIELD), classDesc, this.member.getName(),
701+
CodeFlow.toJvmDescriptor(((Field) this.member).getType()));
696702
}
697703
}
698704
}

0 commit comments

Comments
 (0)