Recover from error during SpEL MIXED mode compilation

Prior to this commit, SpEL was able to recover from an error that
occurred while running a CompiledExpression; however, SpEL was not able
to recover from an error that occurred while compiling the expression
(such as a java.lang.VerifyError). The latter can occur when multiple
threads concurrently change types involved in the expression, such as
the concrete type of a custom variable registered via
EvaluationContext.setVariable(...), which can result in SpEL generating
invalid bytecode.

This commit addresses this issue by catching exceptions thrown while
compiling an expression and updating the `failedAttempts` and
`interpretedCount` counters accordingly. If an exception is caught
while operating in SpelCompilerMode.IMMEDIATE mode, the exception will
be propagated via a SpelEvaluationException with a new
SpelMessage.EXCEPTION_COMPILING_EXPRESSION error category.

Closes gh-28043
This commit is contained in:
Sam Brannen
2022-02-18 15:31:59 +01:00
parent ff20a06876
commit 94af2ca06b
4 changed files with 77 additions and 14 deletions

View File

@@ -76,6 +76,21 @@ class SpelCompilerTests {
assertThat(expression.getValue(context)).asInstanceOf(BOOLEAN).isTrue();
}
@Test // gh-28043
void changingRegisteredVariableTypeDoesNotResultInFailureInMixedMode() {
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.MIXED, null);
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression sharedExpression = parser.parseExpression("#bean.value");
StandardEvaluationContext context = new StandardEvaluationContext();
Object[] beans = new Object[] {new Bean1(), new Bean2(), new Bean3(), new Bean4()};
IntStream.rangeClosed(1, 1_000_000).parallel().forEach(count -> {
context.setVariable("bean", beans[count % 4]);
assertThat(sharedExpression.getValue(context)).asString().startsWith("1");
});
}
static class OrderedComponent implements Ordered {
@@ -121,4 +136,28 @@ class SpelCompilerTests {
boolean hasSomeProperty();
}
public static class Bean1 {
public String getValue() {
return "11";
}
}
public static class Bean2 {
public Integer getValue() {
return 111;
}
}
public static class Bean3 {
public Float getValue() {
return 1.23f;
}
}
public static class Bean4 {
public Character getValue() {
return '1';
}
}
}