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:
parent
ff20a06876
commit
94af2ca06b
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -27,10 +27,11 @@ import java.text.MessageFormat;
|
||||||
* <p>When a message is formatted, it will have this kind of form, capturing the prefix
|
* <p>When a message is formatted, it will have this kind of form, capturing the prefix
|
||||||
* and the error kind:
|
* and the error kind:
|
||||||
*
|
*
|
||||||
* <pre class="code">EL1004E: Type cannot be found 'String'</pre>
|
* <pre class="code">EL1005E: Type cannot be found 'String'</pre>
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public enum SpelMessage {
|
public enum SpelMessage {
|
||||||
|
@ -255,7 +256,11 @@ public enum SpelMessage {
|
||||||
|
|
||||||
/** @since 4.3.17 */
|
/** @since 4.3.17 */
|
||||||
FLAWED_PATTERN(Kind.ERROR, 1073,
|
FLAWED_PATTERN(Kind.ERROR, 1073,
|
||||||
"Failed to efficiently evaluate pattern ''{0}'': consider redesigning it");
|
"Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"),
|
||||||
|
|
||||||
|
/** @since 5.3.16 */
|
||||||
|
EXCEPTION_COMPILING_EXPRESSION(Kind.ERROR, 1074,
|
||||||
|
"An exception occurred while compiling an expression");
|
||||||
|
|
||||||
|
|
||||||
private final Kind kind;
|
private final Kind kind;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2020 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -110,7 +110,8 @@ public final class SpelCompiler implements Opcodes {
|
||||||
return ReflectionUtils.accessibleConstructor(clazz).newInstance();
|
return ReflectionUtils.accessibleConstructor(clazz).newInstance();
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
throw new IllegalStateException("Failed to instantiate CompiledExpression", ex);
|
throw new IllegalStateException("Failed to instantiate CompiledExpression for expression: " +
|
||||||
|
expression.toStringAST(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2020 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -44,6 +44,7 @@ import org.springframework.util.Assert;
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public class SpelExpression implements Expression {
|
public class SpelExpression implements Expression {
|
||||||
|
@ -522,17 +523,34 @@ public class SpelExpression implements Expression {
|
||||||
// Compiled by another thread before this thread got into the sync block
|
// Compiled by another thread before this thread got into the sync block
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
|
try {
|
||||||
compiledAst = compiler.compile(this.ast);
|
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
|
||||||
if (compiledAst != null) {
|
compiledAst = compiler.compile(this.ast);
|
||||||
// Successfully compiled
|
if (compiledAst != null) {
|
||||||
this.compiledAst = compiledAst;
|
// Successfully compiled
|
||||||
return true;
|
this.compiledAst = compiledAst;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Failed to compile
|
||||||
|
this.failedAttempts.incrementAndGet();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
catch (Exception ex) {
|
||||||
// Failed to compile
|
// Failed to compile
|
||||||
this.failedAttempts.incrementAndGet();
|
this.failedAttempts.incrementAndGet();
|
||||||
return false;
|
|
||||||
|
// If running in mixed mode, revert to interpreted
|
||||||
|
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
|
||||||
|
this.compiledAst = null;
|
||||||
|
this.interpretedCount.set(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
|
||||||
|
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_COMPILING_EXPRESSION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,21 @@ class SpelCompilerTests {
|
||||||
assertThat(expression.getValue(context)).asInstanceOf(BOOLEAN).isTrue();
|
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 {
|
static class OrderedComponent implements Ordered {
|
||||||
|
|
||||||
|
@ -121,4 +136,28 @@ class SpelCompilerTests {
|
||||||
boolean hasSomeProperty();
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue