Skip to content

Commit 5cba32d

Browse files
committed
Polish SpEL's VariableReference
1 parent 11c40b5 commit 5cba32d

File tree

1 file changed

+38
-20
lines changed

1 file changed

+38
-20
lines changed

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

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,20 @@
2929
import org.springframework.lang.Nullable;
3030

3131
/**
32-
* Represents a variable reference — for example, {@code #someVar}.
32+
* Represents a variable reference — for example, {@code #root}, {@code #this},
33+
* {@code #someVar}, etc.
3334
*
3435
* @author Andy Clement
3536
* @author Sam Brannen
3637
* @since 3.0
3738
*/
3839
public class VariableReference extends SpelNodeImpl {
3940

40-
// Well known variables:
41-
private static final String THIS = "this"; // currently active context object
41+
/** Currently active context object. */
42+
private static final String THIS = "this";
4243

43-
private static final String ROOT = "root"; // root context object
44+
/** Root context object. */
45+
private static final String ROOT = "root";
4446

4547

4648
private final String name;
@@ -54,41 +56,56 @@ public VariableReference(String variableName, int startPos, int endPos) {
5456

5557
@Override
5658
public ValueRef getValueRef(ExpressionState state) throws SpelEvaluationException {
57-
if (this.name.equals(THIS)) {
59+
if (THIS.equals(this.name)) {
5860
return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(), this);
5961
}
60-
if (this.name.equals(ROOT)) {
62+
if (ROOT.equals(this.name)) {
6163
return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(), this);
6264
}
6365
TypedValue result = state.lookupVariable(this.name);
64-
// a null value will mean either the value was null or the variable was not found
66+
// A null value in the returned VariableRef will mean either the value was
67+
// null or the variable was not found.
6568
return new VariableRef(this.name, result, state.getEvaluationContext());
6669
}
6770

6871
@Override
6972
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
70-
if (this.name.equals(THIS)) {
73+
if (THIS.equals(this.name)) {
7174
return state.getActiveContextObject();
7275
}
73-
if (this.name.equals(ROOT)) {
76+
if (ROOT.equals(this.name)) {
7477
TypedValue result = state.getRootContextObject();
7578
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue());
7679
return result;
7780
}
81+
7882
TypedValue result = state.lookupVariable(this.name);
79-
Object value = result.getValue();
83+
setExitTypeDescriptor(result.getValue());
84+
85+
// A null value in the returned TypedValue will mean either the value was
86+
// null or the variable was not found.
87+
return result;
88+
}
89+
90+
/**
91+
* Set the exit type descriptor for the supplied value.
92+
* <p>If the value is {@code null}, we set the exit type descriptor to
93+
* {@link Object}.
94+
* <p>If the value's type is not public, {@link #generateCode} would insert
95+
* a checkcast to the non-public type in the generated byte code which would
96+
* result in an {@link IllegalAccessError} when the compiled byte code is
97+
* invoked. Thus, as a preventative measure, we set the exit type descriptor
98+
* to {@code Object} in such cases. If resorting to {@code Object} is not
99+
* sufficient, we could consider traversing the hierarchy to find the first
100+
* public type.
101+
*/
102+
private void setExitTypeDescriptor(@Nullable Object value) {
80103
if (value == null || !Modifier.isPublic(value.getClass().getModifiers())) {
81-
// If the type is not public then when generateCode produces a checkcast to it
82-
// then an IllegalAccessError will occur.
83-
// If resorting to Object isn't sufficient, the hierarchy could be traversed for
84-
// the first public type.
85104
this.exitTypeDescriptor = "Ljava/lang/Object";
86105
}
87106
else {
88107
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(value);
89108
}
90-
// a null value will mean either the value was null or the variable was not found
91-
return result;
92109
}
93110

94111
@Override
@@ -105,7 +122,7 @@ public String toStringAST() {
105122

106123
@Override
107124
public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
108-
return !(this.name.equals(THIS) || this.name.equals(ROOT));
125+
return !(THIS.equals(this.name) || ROOT.equals(this.name));
109126
}
110127

111128
@Override
@@ -115,13 +132,14 @@ public boolean isCompilable() {
115132

116133
@Override
117134
public void generateCode(MethodVisitor mv, CodeFlow cf) {
118-
if (this.name.equals(ROOT)) {
119-
mv.visitVarInsn(ALOAD,1);
135+
if (ROOT.equals(this.name)) {
136+
mv.visitVarInsn(ALOAD, 1);
120137
}
121138
else {
122139
mv.visitVarInsn(ALOAD, 2);
123140
mv.visitLdcInsn(this.name);
124-
mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext", "lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;",true);
141+
mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext",
142+
"lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;", true);
125143
}
126144
CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor);
127145
cf.pushDescriptor(this.exitTypeDescriptor);

0 commit comments

Comments
 (0)