Revised exitTypeDescriptor handling to avoid NPEs

Issue: SPR-12014
This commit is contained in:
Juergen Hoeller 2014-07-22 16:24:39 +02:00
parent 3013558e3d
commit 4b09fcc67c
7 changed files with 86 additions and 116 deletions

View File

@ -63,7 +63,7 @@ public class CodeFlow implements Opcodes {
* @param descriptor type descriptor for most recently evaluated element
*/
public void pushDescriptor(String descriptor) {
Assert.notNull(descriptor);
Assert.notNull(descriptor, "Descriptor must not be null");
this.compilationScopes.peek().add(descriptor);
}

View File

@ -85,14 +85,9 @@ public class CompoundExpression extends SpelNodeImpl {
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
ValueRef ref = getValueRef(state);
TypedValue result = ref.getValue();
this.exitTypeDescriptor = this.children[this.children.length-1].getExitDescriptor();
this.exitTypeDescriptor = this.children[this.children.length - 1].getExitDescriptor();
return result;
}
@Override
public String getExitDescriptor() {
return this.exitTypeDescriptor;
}
@Override
public void setValue(ExpressionState state, Object value) throws EvaluationException {
@ -118,7 +113,7 @@ public class CompoundExpression extends SpelNodeImpl {
@Override
public boolean isCompilable() {
for (SpelNodeImpl child: children) {
for (SpelNodeImpl child: this.children) {
if (!child.isCompilable()) {
return false;
}
@ -129,16 +124,15 @@ public class CompoundExpression extends SpelNodeImpl {
@Override
public void generateCode(MethodVisitor mv,CodeFlow codeflow) {
// TODO could optimize T(SomeType).staticMethod - no need to generate the T() part
for (int i=0;i<children.length;i++) {
SpelNodeImpl child = children[i];
if (child instanceof TypeReference &&
(i+1) < children.length &&
children[i+1] instanceof MethodReference) {
for (int i = 0; i < this.children.length;i++) {
SpelNodeImpl child = this.children[i];
if (child instanceof TypeReference && (i + 1) < this.children.length &&
this.children[i+1] instanceof MethodReference) {
continue;
}
child.generateCode(mv, codeflow);
}
codeflow.pushDescriptor(this.getExitDescriptor());
codeflow.pushDescriptor(getExitDescriptor());
}
}

View File

@ -22,12 +22,14 @@ import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.util.StringUtils;
/**
* Represents the elvis operator ?:. For an expression "a?:b" if a is not null, the value
* of the expression is "a", if a is null then the value of the expression is "b".
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class Elvis extends SpelNodeImpl {
@ -38,76 +40,38 @@ public class Elvis extends SpelNodeImpl {
/**
* Evaluate the condition and if not null, return it. If it is null return the other
* value.
* Evaluate the condition and if not null, return it.
* If it is null, return the other value.
* @param state the expression state
* @throws EvaluationException if the condition does not evaluate correctly to a
* boolean or there is a problem executing the chosen alternative
* @throws EvaluationException if the condition does not evaluate correctly
* to a boolean or there is a problem executing the chosen alternative
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue value = this.children[0].getValueInternal(state);
if ((value.getValue() != null) && !((value.getValue() instanceof String) &&
((String) value.getValue()).length() == 0)) {
if (!StringUtils.isEmpty(value.getValue())) {
return value;
}
else {
TypedValue result = this.children[1].getValueInternal(state);
if (exitTypeDescriptor == null) {
String testDescriptor = this.children[0].exitTypeDescriptor;
String ifNullDescriptor = this.children[1].exitTypeDescriptor;
if (testDescriptor.equals(ifNullDescriptor)) {
this.exitTypeDescriptor = testDescriptor;
}
else {
this.exitTypeDescriptor = "Ljava/lang/Object";
}
}
computeExitTypeDescriptor();
return result;
}
}
@Override
public String toStringAST() {
return new StringBuilder().append(getChild(0).toStringAST()).append(" ?: ").append(
getChild(1).toStringAST()).toString();
}
private void computeExitTypeDescriptor() {
if (exitTypeDescriptor == null &&
this.children[0].getExitDescriptor()!=null &&
this.children[1].getExitDescriptor()!=null) {
String conditionDescriptor = this.children[0].exitTypeDescriptor;
String ifNullValueDescriptor = this.children[1].exitTypeDescriptor;
if (conditionDescriptor.equals(ifNullValueDescriptor)) {
this.exitTypeDescriptor = conditionDescriptor;
}
else if (conditionDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(ifNullValueDescriptor)) {
this.exitTypeDescriptor = ifNullValueDescriptor;
}
else if (ifNullValueDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(conditionDescriptor)) {
this.exitTypeDescriptor = conditionDescriptor;
}
else {
// Use the easiest to compute common super type
this.exitTypeDescriptor = "Ljava/lang/Object";
}
}
return getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST();
}
@Override
public boolean isCompilable() {
SpelNodeImpl condition = this.children[0];
SpelNodeImpl ifNullValue = this.children[1];
if (!(condition.isCompilable() && ifNullValue.isCompilable())) {
return false;
}
return
condition.getExitDescriptor()!=null &&
ifNullValue.getExitDescriptor()!=null;
return (condition.isCompilable() && ifNullValue.isCompilable() &&
condition.getExitDescriptor() != null && ifNullValue.getExitDescriptor() != null);
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
// exit type descriptor can be null if both components are literal expressions
@ -128,4 +92,25 @@ public class Elvis extends SpelNodeImpl {
codeflow.pushDescriptor(getExitDescriptor());
}
private void computeExitTypeDescriptor() {
if (this.exitTypeDescriptor == null && this.children[0].getExitDescriptor() != null &&
this.children[1].getExitDescriptor() != null) {
String conditionDescriptor = this.children[0].exitTypeDescriptor;
String ifNullValueDescriptor = this.children[1].exitTypeDescriptor;
if (conditionDescriptor.equals(ifNullValueDescriptor)) {
this.exitTypeDescriptor = conditionDescriptor;
}
else if (conditionDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(ifNullValueDescriptor)) {
this.exitTypeDescriptor = ifNullValueDescriptor;
}
else if (ifNullValueDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(conditionDescriptor)) {
this.exitTypeDescriptor = conditionDescriptor;
}
else {
// Use the easiest to compute common super type
this.exitTypeDescriptor = "Ljava/lang/Object";
}
}
}
}

View File

@ -50,11 +50,11 @@ public class FunctionReference extends SpelNodeImpl {
private final String name;
// Captures the most recently used method for the function invocation *if*
// the method can safely be used for compilation (i.e. no argument conversion is
// going on)
// Captures the most recently used method for the function invocation *if* the method
// can safely be used for compilation (i.e. no argument conversion is going on)
private Method method;
public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) {
super(pos,arguments);
this.name = functionName;
@ -63,29 +63,29 @@ public class FunctionReference extends SpelNodeImpl {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue o = state.lookupVariable(this.name);
if (o == null) {
TypedValue value = state.lookupVariable(this.name);
if (value == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, this.name);
}
// Two possibilities: a lambda function or a Java static method registered as a function
if (!(o.getValue() instanceof Method)) {
throw new SpelEvaluationException(SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, o.getClass());
if (!(value.getValue() instanceof Method)) {
throw new SpelEvaluationException(SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, value.getClass());
}
try {
return executeFunctionJLRMethod(state, (Method) o.getValue());
return executeFunctionJLRMethod(state, (Method) value.getValue());
}
catch (SpelEvaluationException se) {
se.setPosition(getStartPosition());
throw se;
catch (SpelEvaluationException ex) {
ex.setPosition(getStartPosition());
throw ex;
}
}
/**
* Execute a function represented as a java.lang.reflect.Method.
*
* @param state the expression evaluation state
* @param the java method to invoke
* @param method the method to invoke
* @return the return value of the invoked Java method
* @throws EvaluationException if there is any problem invoking the method
*/
@ -107,11 +107,10 @@ public class FunctionReference extends SpelNodeImpl {
// Convert arguments if necessary and remap them for varargs if required
if (functionArgs != null) {
TypeConverter converter = state.getEvaluationContext().getTypeConverter();
argumentConversionOccurred |= ReflectionHelper.convertAllArguments(converter, functionArgs, method);
argumentConversionOccurred = ReflectionHelper.convertAllArguments(converter, functionArgs, method);
}
if (method.isVarArgs()) {
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
method.getParameterTypes(), functionArgs);
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(method.getParameterTypes(), functionArgs);
}
try {
@ -161,19 +160,19 @@ public class FunctionReference extends SpelNodeImpl {
@Override
public boolean isCompilable() {
// Don't yet support non-static method compilation.
return method!=null && Modifier.isStatic(method.getModifiers());
return (this.method != null && Modifier.isStatic(this.method.getModifiers()));
}
@Override
public void generateCode(MethodVisitor mv,CodeFlow codeflow) {
String methodDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.','/');
String[] paramDescriptors = CodeFlow.toParamDescriptors(method);
for (int c = 0; c < children.length; c++) {
SpelNodeImpl child = children[c];
String methodDeclaringClassSlashedDescriptor = this.method.getDeclaringClass().getName().replace('.','/');
String[] paramDescriptors = CodeFlow.toParamDescriptors(this.method);
for (int c = 0; c < this.children.length; c++) {
SpelNodeImpl child = this.children[c];
codeflow.enterCompilationScope();
child.generateCode(mv, codeflow);
// Check if need to box it for the method reference?
if (CodeFlow.isPrimitive(codeflow.lastDescriptor()) && (paramDescriptors[c].charAt(0)=='L')) {
if (CodeFlow.isPrimitive(codeflow.lastDescriptor()) && paramDescriptors[c].charAt(0) == 'L') {
CodeFlow.insertBoxIfNecessary(mv, codeflow.lastDescriptor().charAt(0));
}
else if (!codeflow.lastDescriptor().equals(paramDescriptors[c])) {
@ -182,8 +181,9 @@ public class FunctionReference extends SpelNodeImpl {
}
codeflow.exitCompilationScope();
}
mv.visitMethodInsn(INVOKESTATIC,methodDeclaringClassSlashedDescriptor,method.getName(),CodeFlow.createSignatureDescriptor(method),false);
codeflow.pushDescriptor(exitTypeDescriptor);
mv.visitMethodInsn(INVOKESTATIC, methodDeclaringClassSlashedDescriptor, this.method.getName(),
CodeFlow.createSignatureDescriptor(this.method),false);
codeflow.pushDescriptor(this.exitTypeDescriptor);
}
}

View File

@ -91,11 +91,6 @@ public class MethodReference extends SpelNodeImpl {
return result;
}
@Override
public String getExitDescriptor() {
return exitTypeDescriptor;
}
private TypedValue getValueInternal(EvaluationContext evaluationContext,
Object value, TypeDescriptor targetType, Object[] arguments) {
@ -360,13 +355,13 @@ public class MethodReference extends SpelNodeImpl {
boolean itf = method.getDeclaringClass().isInterface();
String methodDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.','/');
if (!isStaticMethod) {
if (descriptor == null || !descriptor.equals(method.getDeclaringClass())) {
mv.visitTypeInsn(CHECKCAST, method.getDeclaringClass().getName().replace('.','/'));
if (descriptor == null || !descriptor.equals(methodDeclaringClassSlashedDescriptor)) {
mv.visitTypeInsn(CHECKCAST, methodDeclaringClassSlashedDescriptor);
}
}
String[] paramDescriptors = CodeFlow.toParamDescriptors(method);
for (int c=0;c<children.length;c++) {
SpelNodeImpl child = children[c];
for (int c = 0; c < this.children.length;c++) {
SpelNodeImpl child = this.children[c];
codeflow.enterCompilationScope();
child.generateCode(mv, codeflow);
// Check if need to box it for the method reference?
@ -379,8 +374,9 @@ public class MethodReference extends SpelNodeImpl {
}
codeflow.exitCompilationScope();
}
mv.visitMethodInsn(isStaticMethod?INVOKESTATIC:INVOKEVIRTUAL,methodDeclaringClassSlashedDescriptor,method.getName(),CodeFlow.createSignatureDescriptor(method), itf);
codeflow.pushDescriptor(exitTypeDescriptor);
mv.visitMethodInsn(isStaticMethod ? INVOKESTATIC : INVOKEVIRTUAL,
methodDeclaringClassSlashedDescriptor, method.getName(), CodeFlow.createSignatureDescriptor(method), itf);
codeflow.pushDescriptor(this.exitTypeDescriptor);
}
}

View File

@ -79,18 +79,13 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
TypedValue tv = getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), state.getConfiguration().isAutoGrowNullReferences());
if (cachedReadAccessor instanceof CompilablePropertyAccessor) {
CompilablePropertyAccessor accessor = (CompilablePropertyAccessor)cachedReadAccessor;
exitTypeDescriptor = CodeFlow.toDescriptor(accessor.getPropertyType());
if (this.cachedReadAccessor instanceof CompilablePropertyAccessor) {
CompilablePropertyAccessor accessor = (CompilablePropertyAccessor) this.cachedReadAccessor;
this.exitTypeDescriptor = CodeFlow.toDescriptor(accessor.getPropertyType());
}
return tv;
}
@Override
public String getExitDescriptor() {
return exitTypeDescriptor;
}
private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext,
boolean isAutoGrowNullReferences) throws EvaluationException {
@ -290,12 +285,14 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
// TODO when there is more time, remove this and use the version in AstUtils
/**
* Determines the set of property resolvers that should be used to try and access a property on the specified target
* type. The resolvers are considered to be in an ordered list, however in the returned list any that are exact
* matches for the input target type (as opposed to 'general' resolvers that could work for any type) are placed at
* the start of the list. In addition, there are specific resolvers that exactly name the class in question and
* resolvers that name a specific class but it is a supertype of the class we have. These are put at the end of the
* specific resolvers set and will be tried after exactly matching accessors but before generic accessors.
* Determines the set of property resolvers that should be used to try and access a property
* on the specified target type. The resolvers are considered to be in an ordered list,
* however in the returned list any that are exact matches for the input target type (as
* opposed to 'general' resolvers that could work for any type) are placed at the start of the
* list. In addition, there are specific resolvers that exactly name the class in question
* and resolvers that name a specific class but it is a supertype of the class we have.
* These are put at the end of the specific resolvers set and will be tried after exactly
* matching accessors but before generic accessors.
* @param contextObject the object upon which property access is being attempted
* @return a list of resolvers that should be tried in order to access the property
*/

View File

@ -56,7 +56,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
* does not include the trailing semicolon (for non array reference types). Some examples:
* Ljava/lang/String, I, [I
*/
protected String exitTypeDescriptor;
protected volatile String exitTypeDescriptor;
public SpelNodeImpl(int pos, SpelNodeImpl... operands) {
@ -76,7 +76,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
SpelNodeImpl result = null;
if (this.parent != null) {
for (SpelNodeImpl child : this.parent.children) {
if (this==child) {
if (this == child) {
break;
}
result = child;
@ -92,18 +92,16 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
if (this.parent != null) {
SpelNodeImpl[] peers = this.parent.children;
for (int i = 0, max = peers.length; i < max; i++) {
if (peers[i] == this) {
if ((i + 1) >= max) {
if (this == peers[i]) {
if (i + 1 >= max) {
return false;
}
Class<?> clazz = peers[i + 1].getClass();
for (Class<?> desiredClazz : clazzes) {
if (clazz.equals(desiredClazz)) {
return true;
}
}
return false;
}
}