Skip to content

Commit a28fc76

Browse files
committed
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
1 parent 9d944fb commit a28fc76

File tree

4 files changed

+139
-9
lines changed

4 files changed

+139
-9
lines changed

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,16 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) {
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+
} else {
301+
if (isStaticMethod) {
302+
// Something on the stack when nothing is needed
303+
mv.visitInsn(POP);
304+
}
297305
}
298306

299307
if (CodeFlow.isPrimitive(descriptor)) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,12 @@ public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
685685
if (descriptor == null || !memberDeclaringClassSlashedDescriptor.equals(descriptor.substring(1))) {
686686
mv.visitTypeInsn(CHECKCAST, memberDeclaringClassSlashedDescriptor);
687687
}
688+
} else {
689+
if (descriptor != null) {
690+
// A static field/method call will not consume what is on the stack,
691+
// it needs to be popped off.
692+
mv.visitInsn(POP);
693+
}
688694
}
689695
if (this.member instanceof Field) {
690696
mv.visitFieldInsn(isStatic ? GETSTATIC : GETFIELD, memberDeclaringClassSlashedDescriptor,

spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2958,6 +2958,103 @@ public void failsWhenSettingContextForExpression_SPR12326() {
29582958
assertTrue(expression.getValue(Boolean.class));
29592959
}
29602960

2961+
2962+
/**
2963+
* Test variants of using T(...) and static/non-static method/property/field references.
2964+
*/
2965+
@Test
2966+
public void constructorReference_SPR13781() {
2967+
// Static field access on a T() referenced type
2968+
expression = parser.parseExpression("T(java.util.Locale).ENGLISH");
2969+
assertEquals("en",expression.getValue().toString());
2970+
assertCanCompile(expression);
2971+
assertEquals("en",expression.getValue().toString());
2972+
2973+
// The actual expression from the bug report. It fails if the ENGLISH reference fails
2974+
// to pop the type reference for Locale off the stack (if it isn't popped then
2975+
// toLowerCase() will be called with a Locale parameter). In this situation the
2976+
// code generation for ENGLISH should notice there is something on the stack that
2977+
// is not required and pop it off.
2978+
expression = parser.parseExpression("#userId.toString().toLowerCase(T(java.util.Locale).ENGLISH)");
2979+
StandardEvaluationContext context =
2980+
new StandardEvaluationContext();
2981+
context.setVariable("userId", "RoDnEy");
2982+
assertEquals("rodney",expression.getValue(context));
2983+
assertCanCompile(expression);
2984+
assertEquals("rodney",expression.getValue(context));
2985+
2986+
// Property access on a class object
2987+
expression = parser.parseExpression("T(String).name");
2988+
assertEquals("java.lang.String",expression.getValue());
2989+
assertCanCompile(expression);
2990+
assertEquals("java.lang.String",expression.getValue());
2991+
2992+
// Now the type reference isn't on the stack, and needs loading
2993+
context = new StandardEvaluationContext(String.class);
2994+
expression = parser.parseExpression("name");
2995+
assertEquals("java.lang.String",expression.getValue(context));
2996+
assertCanCompile(expression);
2997+
assertEquals("java.lang.String",expression.getValue(context));
2998+
2999+
expression = parser.parseExpression("T(String).getName()");
3000+
assertEquals("java.lang.String",expression.getValue());
3001+
assertCanCompile(expression);
3002+
assertEquals("java.lang.String",expression.getValue());
3003+
3004+
// These tests below verify that the chain of static accesses (either method/property or field)
3005+
// leave the right thing on top of the stack for processing by any outer consuming code.
3006+
// Here the consuming code is the String.valueOf() function. If the wrong thing were on
3007+
// the stack (for example if the compiled code for static methods wasn't popping the
3008+
// previous thing off the stack) the valueOf() would operate on the wrong value.
3009+
3010+
String shclass = StaticsHelper.class.getName();
3011+
// Basic chain: property access then method access
3012+
expression = parser.parseExpression("T(String).valueOf(T(String).name.valueOf(1))");
3013+
assertEquals("1",expression.getValue());
3014+
assertCanCompile(expression);
3015+
assertEquals("1",expression.getValue());
3016+
3017+
// chain of statics ending with static method
3018+
expression = parser.parseExpression("T(String).valueOf(T("+shclass+").methoda().methoda().methodb())");
3019+
assertEquals("mb",expression.getValue());
3020+
assertCanCompile(expression);
3021+
assertEquals("mb",expression.getValue());
3022+
3023+
// chain of statics ending with static field
3024+
expression = parser.parseExpression("T(String).valueOf(T("+shclass+").fielda.fielda.fieldb)");
3025+
assertEquals("fb",expression.getValue());
3026+
assertCanCompile(expression);
3027+
assertEquals("fb",expression.getValue());
3028+
3029+
// chain of statics ending with static property access
3030+
expression = parser.parseExpression("T(String).valueOf(T("+shclass+").propertya.propertya.propertyb)");
3031+
assertEquals("pb",expression.getValue());
3032+
assertCanCompile(expression);
3033+
assertEquals("pb",expression.getValue());
3034+
3035+
// variety chain
3036+
expression = parser.parseExpression("T(String).valueOf(T("+shclass+").fielda.methoda().propertya.fieldb)");
3037+
assertEquals("fb",expression.getValue());
3038+
assertCanCompile(expression);
3039+
assertEquals("fb",expression.getValue());
3040+
3041+
expression = parser.parseExpression("T(String).valueOf(fielda.fieldb)");
3042+
assertEquals("fb",expression.getValue(StaticsHelper.sh));
3043+
assertCanCompile(expression);
3044+
assertEquals("fb",expression.getValue(StaticsHelper.sh));
3045+
3046+
expression = parser.parseExpression("T(String).valueOf(propertya.propertyb)");
3047+
assertEquals("pb",expression.getValue(StaticsHelper.sh));
3048+
assertCanCompile(expression);
3049+
assertEquals("pb",expression.getValue(StaticsHelper.sh));
3050+
3051+
expression = parser.parseExpression("T(String).valueOf(methoda().methodb())");
3052+
assertEquals("mb",expression.getValue(StaticsHelper.sh));
3053+
assertCanCompile(expression);
3054+
assertEquals("mb",expression.getValue(StaticsHelper.sh));
3055+
3056+
}
3057+
29613058
@Test
29623059
public void constructorReference_SPR12326() {
29633060
String type = this.getClass().getName();
@@ -5341,4 +5438,29 @@ public static String format(String s, Object... args) {
53415438
}
53425439
}
53435440

5441+
public static class StaticsHelper {
5442+
static StaticsHelper sh = new StaticsHelper();
5443+
public static StaticsHelper methoda() {
5444+
return sh;
5445+
}
5446+
public static String methodb() {
5447+
return "mb";
5448+
}
5449+
5450+
public static StaticsHelper getPropertya() {
5451+
return sh;
5452+
}
5453+
5454+
public static String getPropertyb() {
5455+
return "pb";
5456+
}
5457+
5458+
5459+
public static StaticsHelper fielda = sh;
5460+
public static String fieldb = "fb";
5461+
5462+
public String toString() {
5463+
return "sh";
5464+
}
5465+
}
53445466
}

0 commit comments

Comments
 (0)