Skip to content

Commit

Permalink
Recover from error during SpEL MIXED mode compilation
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Feb 18, 2022
1 parent ff20a06 commit 94af2ca
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 14 deletions.
@@ -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");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,10 +27,11 @@
* <p>When a message is formatted, it will have this kind of form, capturing the prefix
* 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 Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public enum SpelMessage {
Expand Down Expand Up @@ -255,7 +256,11 @@ public enum SpelMessage {

/** @since 4.3.17 */
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;
Expand Down
@@ -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");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -110,7 +110,8 @@ public CompiledExpression compile(SpelNodeImpl expression) {
return ReflectionUtils.accessibleConstructor(clazz).newInstance();
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to instantiate CompiledExpression", ex);
throw new IllegalStateException("Failed to instantiate CompiledExpression for expression: " +
expression.toStringAST(), ex);
}
}
}
Expand Down
@@ -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");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -44,6 +44,7 @@
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public class SpelExpression implements Expression {
Expand Down Expand Up @@ -522,17 +523,34 @@ public boolean compileExpression() {
// Compiled by another thread before this thread got into the sync block
return true;
}
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
compiledAst = compiler.compile(this.ast);
if (compiledAst != null) {
// Successfully compiled
this.compiledAst = compiledAst;
return true;
try {
SpelCompiler compiler = SpelCompiler.getCompiler(this.configuration.getCompilerClassLoader());
compiledAst = compiler.compile(this.ast);
if (compiledAst != null) {
// Successfully compiled
this.compiledAst = compiledAst;
return true;
}
else {
// Failed to compile
this.failedAttempts.incrementAndGet();
return false;
}
}
else {
catch (Exception ex) {
// Failed to compile
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);
}
}
}
}
Expand Down
Expand Up @@ -76,6 +76,21 @@ void defaultMethodInvocation() {
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 {

Expand Down Expand Up @@ -121,4 +136,28 @@ default boolean isEditable2() {
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';
}
}

}

0 comments on commit 94af2ca

Please sign in to comment.