Skip to content

Commit a31f0bb

Browse files
committed
Fix compilation of expressions using instanceof and primitives
Prior to this commit the SpEL compiler would generate bad bytecode if the left hand operand of an instanceof was a primitive or if the right hand operand was a primitive type reference. With the fixes primitives on the left hand side are now correctly boxed and special handling is in place for when the right hand side is a primitive type reference. Using a primitive type reference on the right always causes the instanceof check to return false. Additionally a guard has been added such that compilation is not allowed when the right hand side of an expression is not a type reference. If it is, for example, a variable reference that evaluates to a type reference then that cannot be expressed in bytecode so compilation is not performed. Issue: SPR-14250
1 parent 3c92ddc commit a31f0bb

File tree

2 files changed

+66
-5
lines changed

2 files changed

+66
-5
lines changed

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -53,8 +53,9 @@ public OperatorInstanceof(int pos, SpelNodeImpl... operands) {
5353
*/
5454
@Override
5555
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
56+
SpelNodeImpl rightOperand = getRightOperand();
5657
TypedValue left = getLeftOperand().getValueInternal(state);
57-
TypedValue right = getRightOperand().getValueInternal(state);
58+
TypedValue right = rightOperand.getValueInternal(state);
5859
Object leftValue = left.getValue();
5960
Object rightValue = right.getValue();
6061
BooleanTypedValue result = null;
@@ -71,7 +72,11 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati
7172
result = BooleanTypedValue.forValue(rightClass.isAssignableFrom(leftValue.getClass()));
7273
}
7374
this.type = rightClass;
74-
this.exitTypeDescriptor = "Z";
75+
if (rightOperand instanceof TypeReference) {
76+
// Can only generate bytecode where the right operand is a direct type reference,
77+
// not if it is indirect (for example when right operand is a variable reference)
78+
this.exitTypeDescriptor = "Z";
79+
}
7580
return result;
7681
}
7782

@@ -83,7 +88,16 @@ public boolean isCompilable() {
8388
@Override
8489
public void generateCode(MethodVisitor mv, CodeFlow cf) {
8590
getLeftOperand().generateCode(mv, cf);
86-
mv.visitTypeInsn(INSTANCEOF,Type.getInternalName(this.type));
91+
CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor());
92+
if (this.type.isPrimitive()) {
93+
// always false - but left operand code always driven
94+
// in case it had side effects
95+
mv.visitInsn(POP);
96+
mv.visitInsn(ICONST_0); // value of false
97+
}
98+
else {
99+
mv.visitTypeInsn(INSTANCEOF,Type.getInternalName(this.type));
100+
}
87101
cf.pushDescriptor(this.exitTypeDescriptor);
88102
}
89103

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -235,6 +235,53 @@ public void operatorInstanceOf() throws Exception {
235235
assertEquals(true,expression.getValue(root));
236236
}
237237

238+
@Test
239+
public void operatorInstanceOf_SPR14250() throws Exception {
240+
// primitive left operand - should get boxed, return true
241+
expression = parse("3 instanceof T(Integer)");
242+
assertEquals(true,expression.getValue());
243+
assertCanCompile(expression);
244+
assertEquals(true,expression.getValue());
245+
246+
// primitive left operand - should get boxed, return false
247+
expression = parse("3 instanceof T(String)");
248+
assertEquals(false,expression.getValue());
249+
assertCanCompile(expression);
250+
assertEquals(false,expression.getValue());
251+
252+
// double slot left operand - should get boxed, return false
253+
expression = parse("3.0d instanceof T(Integer)");
254+
assertEquals(false,expression.getValue());
255+
assertCanCompile(expression);
256+
assertEquals(false,expression.getValue());
257+
258+
// double slot left operand - should get boxed, return true
259+
expression = parse("3.0d instanceof T(Double)");
260+
assertEquals(true,expression.getValue());
261+
assertCanCompile(expression);
262+
assertEquals(true,expression.getValue());
263+
264+
// Only when the right hand operand is a direct type reference
265+
// will it be compilable.
266+
StandardEvaluationContext ctx = new StandardEvaluationContext();
267+
ctx.setVariable("foo", String.class);
268+
expression = parse("3 instanceof #foo");
269+
assertEquals(false,expression.getValue(ctx));
270+
assertCantCompile(expression);
271+
272+
// use of primitive as type for instanceof check - compilable
273+
// but always false
274+
expression = parse("3 instanceof T(int)");
275+
assertEquals(false,expression.getValue());
276+
assertCanCompile(expression);
277+
assertEquals(false,expression.getValue());
278+
279+
expression = parse("3 instanceof T(long)");
280+
assertEquals(false,expression.getValue());
281+
assertCanCompile(expression);
282+
assertEquals(false,expression.getValue());
283+
}
284+
238285
@Test
239286
public void stringLiteral() throws Exception {
240287
expression = parser.parseExpression("'abcde'");

0 commit comments

Comments
 (0)