Skip to content

Commit 94af2ca

Browse files
committed
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
1 parent ff20a06 commit 94af2ca

File tree

4 files changed

+77
-14
lines changed

4 files changed

+77
-14
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -27,10 +27,11 @@
2727
* <p>When a message is formatted, it will have this kind of form, capturing the prefix
2828
* and the error kind:
2929
*
30-
* <pre class="code">EL1004E: Type cannot be found 'String'</pre>
30+
* <pre class="code">EL1005E: Type cannot be found 'String'</pre>
3131
*
3232
* @author Andy Clement
3333
* @author Juergen Hoeller
34+
* @author Sam Brannen
3435
* @since 3.0
3536
*/
3637
public enum SpelMessage {
@@ -255,7 +256,11 @@ public enum SpelMessage {
255256

256257
/** @since 4.3.17 */
257258
FLAWED_PATTERN(Kind.ERROR, 1073,
258-
"Failed to efficiently evaluate pattern ''{0}'': consider redesigning it");
259+
"Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"),
260+
261+
/** @since 5.3.16 */
262+
EXCEPTION_COMPILING_EXPRESSION(Kind.ERROR, 1074,
263+
"An exception occurred while compiling an expression");
259264

260265

261266
private final Kind kind;

spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -110,7 +110,8 @@ public CompiledExpression compile(SpelNodeImpl expression) {
110110
return ReflectionUtils.accessibleConstructor(clazz).newInstance();
111111
}
112112
catch (Throwable ex) {
113-
throw new IllegalStateException("Failed to instantiate CompiledExpression", ex);
113+
throw new IllegalStateException("Failed to instantiate CompiledExpression for expression: " +
114+
expression.toStringAST(), ex);
114115
}
115116
}
116117
}

spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -44,6 +44,7 @@
4444
*
4545
* @author Andy Clement
4646
* @author Juergen Hoeller
47+
* @author Sam Brannen
4748
* @since 3.0
4849
*/
4950
public class SpelExpression implements Expression {
@@ -522,17 +523,34 @@ public boolean compileExpression() {
522523
// Compiled by another thread before this thread got into the sync block
523524
return true;
524525
}
525-
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
526-
compiledAst = compiler.compile(this.ast);
527-
if (compiledAst != null) {
528-
// Successfully compiled
529-
this.compiledAst = compiledAst;
530-
return true;
526+
try {
527+
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
528+
compiledAst = compiler.compile(this.ast);
529+
if (compiledAst != null) {
530+
// Successfully compiled
531+
this.compiledAst = compiledAst;
532+
return true;
533+
}
534+
else {
535+
// Failed to compile
536+
this.failedAttempts.incrementAndGet();
537+
return false;
538+
}
531539
}
532-
else {
540+
catch (Exception ex) {
533541
// Failed to compile
534542
this.failedAttempts.incrementAndGet();
535-
return false;
543+
544+
// If running in mixed mode, revert to interpreted
545+
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
546+
this.compiledAst = null;
547+
this.interpretedCount.set(0);
548+
return false;
549+
}
550+
else {
551+
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
552+
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_COMPILING_EXPRESSION);
553+
}
536554
}
537555
}
538556
}

spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ void defaultMethodInvocation() {
7676
assertThat(expression.getValue(context)).asInstanceOf(BOOLEAN).isTrue();
7777
}
7878

79+
@Test // gh-28043
80+
void changingRegisteredVariableTypeDoesNotResultInFailureInMixedMode() {
81+
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.MIXED, null);
82+
SpelExpressionParser parser = new SpelExpressionParser(config);
83+
Expression sharedExpression = parser.parseExpression("#bean.value");
84+
StandardEvaluationContext context = new StandardEvaluationContext();
85+
86+
Object[] beans = new Object[] {new Bean1(), new Bean2(), new Bean3(), new Bean4()};
87+
88+
IntStream.rangeClosed(1, 1_000_000).parallel().forEach(count -> {
89+
context.setVariable("bean", beans[count % 4]);
90+
assertThat(sharedExpression.getValue(context)).asString().startsWith("1");
91+
});
92+
}
93+
7994

8095
static class OrderedComponent implements Ordered {
8196

@@ -121,4 +136,28 @@ default boolean isEditable2() {
121136
boolean hasSomeProperty();
122137
}
123138

139+
public static class Bean1 {
140+
public String getValue() {
141+
return "11";
142+
}
143+
}
144+
145+
public static class Bean2 {
146+
public Integer getValue() {
147+
return 111;
148+
}
149+
}
150+
151+
public static class Bean3 {
152+
public Float getValue() {
153+
return 1.23f;
154+
}
155+
}
156+
157+
public static class Bean4 {
158+
public Character getValue() {
159+
return '1';
160+
}
161+
}
162+
124163
}

0 commit comments

Comments
 (0)