Fix SpEL compilation of constructor invocation

The argument processing for compiling constructor references
was very basic and this fix removes that and ensures the
comprehensive logic written for method argument processing
(under SPR-12328) is now used for both method and constructor
argument handling. This fixes the reported issue and ensures
varargs constructor references can be compiled.

This also includes a couple of small fixes for the secondary
testcase reported in SPR-12326. The first is to ensure the
right root context object is used when it is passed
to getValue() indirectly through the evaluation context.
The final fix is to ensure correct boxing of primitives is
done when a method is called upon a primitive.

Issue: SPR-12326
This commit is contained in:
Andy Clement
2014-10-17 10:26:54 -07:00
parent c5e360d886
commit aae221cb15
7 changed files with 252 additions and 21 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.expression.spel;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -837,12 +838,23 @@ public class CodeFlow implements Opcodes {
* packaged into an array.
* @param mv the method visitor where code should be generated
* @param cf the current codeflow
* @param method the method for which arguments are being setup
* @param member the method or constructor for which arguments are being setup
* @param arguments the expression nodes for the expression supplied argument values
*/
public static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Method method, SpelNodeImpl[] arguments) {
String[] paramDescriptors = CodeFlow.toParamDescriptors(method);
if (method.isVarArgs()) {
public static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
String[] paramDescriptors = null;
boolean isVarargs = false;
if (member instanceof Constructor) {
Constructor<?> ctor = (Constructor<?>)member;
paramDescriptors = toDescriptors(ctor.getParameterTypes());
isVarargs = ctor.isVarArgs();
}
else { // Method
Method method = (Method)member;
paramDescriptors = toDescriptors(method.getParameterTypes());
isVarargs = method.isVarArgs();
}
if (isVarargs) {
// The final parameter may or may not need packaging into an array, or nothing may
// have been passed to satisfy the varargs and so something needs to be built.
int p = 0; // Current supplied argument being processed

View File

@@ -437,29 +437,21 @@ public class ConstructorReference extends SpelNodeImpl {
ReflectiveConstructorExecutor executor = (ReflectiveConstructorExecutor) this.cachedExecutor;
Constructor<?> constructor = executor.getConstructor();
return (!constructor.isVarArgs() && Modifier.isPublic(constructor.getModifiers()) &&
return (Modifier.isPublic(constructor.getModifiers()) &&
Modifier.isPublic(constructor.getDeclaringClass().getModifiers()));
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow cf) {
ReflectiveConstructorExecutor executor = ((ReflectiveConstructorExecutor) this.cachedExecutor);
Constructor<?> constructor = executor.getConstructor();
Constructor<?> constructor = executor.getConstructor();
String classSlashedDescriptor = constructor.getDeclaringClass().getName().replace('.', '/');
String[] paramDescriptors = CodeFlow.toParamDescriptors(constructor);
mv.visitTypeInsn(NEW, classSlashedDescriptor);
mv.visitInsn(DUP);
for (int c = 1; c < this.children.length; c++) { // children[0] is the type of the constructor
SpelNodeImpl child = this.children[c];
cf.enterCompilationScope();
child.generateCode(mv, cf);
// Check if need to box it for the method reference?
if (CodeFlow.isPrimitive(cf.lastDescriptor()) && paramDescriptors[c-1].charAt(0) == 'L') {
CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor().charAt(0));
}
cf.exitCompilationScope();
}
// children[0] is the type of the constructor, don't want to include that in argument processing
SpelNodeImpl[] arguments = new SpelNodeImpl[children.length-1];
System.arraycopy(children, 1, arguments, 0, children.length-1);
CodeFlow.generateCodeForArguments(mv, cf, constructor, arguments);
mv.visitMethodInsn(INVOKESPECIAL, classSlashedDescriptor, "<init>",
CodeFlow.createSignatureDescriptor(constructor), false);
cf.pushDescriptor(this.exitTypeDescriptor);

View File

@@ -295,6 +295,10 @@ public class MethodReference extends SpelNodeImpl {
if (descriptor == null && !isStaticMethod) {
cf.loadTarget(mv);
}
if (CodeFlow.isPrimitive(descriptor)) {
CodeFlow.insertBoxIfNecessary(mv, descriptor.charAt(0));
}
boolean itf = method.getDeclaringClass().isInterface();
String methodDeclaringClassSlashedDescriptor = null;

View File

@@ -221,7 +221,8 @@ public class SpelExpression implements Expression {
Assert.notNull(context, "The EvaluationContext is required");
if (compiledAst!= null) {
try {
Object result = this.compiledAst.getValue(null,context);
TypedValue contextRoot = context == null ? null : context.getRootObject();
Object result = this.compiledAst.getValue(contextRoot==null?null:contextRoot.getValue(),context);
return result;
}
catch (Throwable ex) {
@@ -272,7 +273,8 @@ public class SpelExpression implements Expression {
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
if (this.compiledAst != null) {
try {
Object result = this.compiledAst.getValue(null,context);
TypedValue contextRoot = context == null ? null : context.getRootObject();
Object result = this.compiledAst.getValue(contextRoot==null?null:contextRoot.getValue(),context);
if (expectedResultType != null) {
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
}