Fix VerifyError for SpEL ternary compilation
The ternary expression node was failing to generate the necessary unboxing bytecode when the condition part of the expression returned a boxed Boolean rather than a primitive boolean. Also fixed here is an IllegalAccessError that was seen in the same expression due to generating a CHECKCAST bytecode for a private type. Issue: SPR-12271
This commit is contained in:
parent
b2d67914a8
commit
bd7d56ac54
|
@ -446,11 +446,13 @@ public class CodeFlow implements Opcodes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (!descriptor.equals("Ljava/lang/Object")) {
|
||||||
// This is chopping off the 'L' to leave us with "java/lang/String"
|
// This is chopping off the 'L' to leave us with "java/lang/String"
|
||||||
mv.visitTypeInsn(CHECKCAST, descriptor.substring(1));
|
mv.visitTypeInsn(CHECKCAST, descriptor.substring(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the appropriate boxing instruction for a specific type (if it needs
|
* Determine the appropriate boxing instruction for a specific type (if it needs
|
||||||
|
|
|
@ -109,6 +109,9 @@ public class Ternary extends SpelNodeImpl {
|
||||||
computeExitTypeDescriptor();
|
computeExitTypeDescriptor();
|
||||||
codeflow.enterCompilationScope();
|
codeflow.enterCompilationScope();
|
||||||
this.children[0].generateCode(mv, codeflow);
|
this.children[0].generateCode(mv, codeflow);
|
||||||
|
if (!CodeFlow.isPrimitive(codeflow.lastDescriptor())) {
|
||||||
|
CodeFlow.insertUnboxInsns(mv, 'Z', codeflow.lastDescriptor());
|
||||||
|
}
|
||||||
codeflow.exitCompilationScope();
|
codeflow.exitCompilationScope();
|
||||||
Label elseTarget = new Label();
|
Label elseTarget = new Label();
|
||||||
Label endOfIf = new Label();
|
Label endOfIf = new Label();
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.expression.spel.ast;
|
package org.springframework.expression.spel.ast;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
import org.springframework.asm.MethodVisitor;
|
import org.springframework.asm.MethodVisitor;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.TypedValue;
|
import org.springframework.expression.TypedValue;
|
||||||
|
@ -71,7 +73,17 @@ public class VariableReference extends SpelNodeImpl {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
TypedValue result = state.lookupVariable(this.name);
|
TypedValue result = state.lookupVariable(this.name);
|
||||||
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue());
|
Object value = result.getValue();
|
||||||
|
if (value == null || !Modifier.isPublic(value.getClass().getModifiers())) {
|
||||||
|
// If the type is not public then when generateCode produces a checkcast to it
|
||||||
|
// then an IllegalAccessError will occur.
|
||||||
|
// If resorting to Object isn't sufficient, the hierarchy could be traversed for
|
||||||
|
// the first public type.
|
||||||
|
this.exitTypeDescriptor ="Ljava/lang/Object";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(value);
|
||||||
|
}
|
||||||
// a null value will mean either the value was null or the variable was not found
|
// a null value will mean either the value was null or the variable was not found
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import java.util.Map;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.asm.MethodVisitor;
|
import org.springframework.asm.MethodVisitor;
|
||||||
import org.springframework.expression.AccessException;
|
import org.springframework.expression.AccessException;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
@ -522,6 +521,19 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
||||||
assertEquals(1,expression.getValue(root));
|
assertEquals(1,expression.getValue(root));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ternaryWithBooleanReturn() { // SPR-12271
|
||||||
|
expression = parser.parseExpression("T(Boolean).TRUE?'abc':'def'");
|
||||||
|
assertEquals("abc",expression.getValue());
|
||||||
|
assertCanCompile(expression);
|
||||||
|
assertEquals("abc",expression.getValue());
|
||||||
|
|
||||||
|
expression = parser.parseExpression("T(Boolean).FALSE?'abc':'def'");
|
||||||
|
assertEquals("def",expression.getValue());
|
||||||
|
assertCanCompile(expression);
|
||||||
|
assertEquals("def",expression.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void elvis() throws Exception {
|
public void elvis() throws Exception {
|
||||||
Expression expression = parser.parseExpression("'a'?:'b'");
|
Expression expression = parser.parseExpression("'a'?:'b'");
|
||||||
|
@ -1830,7 +1842,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
||||||
assertEquals('c',resultC);
|
assertEquals('c',resultC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compoundExpression() throws Exception {
|
public void compoundExpression() throws Exception {
|
||||||
Payload payload = new Payload();
|
Payload payload = new Payload();
|
||||||
|
@ -1915,6 +1926,17 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
||||||
assertEquals("value4",expression.getValue(tc));
|
assertEquals("value4",expression.getValue(tc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertyReferenceVisibility() { // SPR-12771
|
||||||
|
StandardEvaluationContext ctx = new StandardEvaluationContext();
|
||||||
|
ctx.setVariable("httpServletRequest", HttpServlet3RequestFactory.getOne());
|
||||||
|
// Without a fix compilation was inserting a checkcast to a private type
|
||||||
|
expression = parser.parseExpression("#httpServletRequest.servletPath");
|
||||||
|
assertEquals("wibble",expression.getValue(ctx));
|
||||||
|
assertCanCompile(expression);
|
||||||
|
assertEquals("wibble",expression.getValue(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Test
|
@Test
|
||||||
public void indexer() throws Exception {
|
public void indexer() throws Exception {
|
||||||
|
@ -2954,4 +2976,28 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
||||||
public TestClass9(int i) {}
|
public TestClass9(int i) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These test classes simulate a pattern of public/private classes seen in Spring Security
|
||||||
|
|
||||||
|
// final class HttpServlet3RequestFactory implements HttpServletRequestFactory
|
||||||
|
static class HttpServlet3RequestFactory {
|
||||||
|
|
||||||
|
static Servlet3SecurityContextHolderAwareRequestWrapper getOne() {
|
||||||
|
HttpServlet3RequestFactory outer = new HttpServlet3RequestFactory();
|
||||||
|
return outer.new Servlet3SecurityContextHolderAwareRequestWrapper();
|
||||||
|
}
|
||||||
|
// private class Servlet3SecurityContextHolderAwareRequestWrapper extends SecurityContextHolderAwareRequestWrapper
|
||||||
|
private class Servlet3SecurityContextHolderAwareRequestWrapper extends SecurityContextHolderAwareRequestWrapper {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequestWrapper
|
||||||
|
static class SecurityContextHolderAwareRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HttpServletRequestWrapper {
|
||||||
|
public String getServletPath() {
|
||||||
|
return "wibble";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue