diff --git a/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java index 0f7ef0f7927..005903ca0ee 100644 --- a/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java @@ -185,4 +185,26 @@ public interface EvaluationContext { return true; } + /** + * Determine if compilation is supported within expressions evaluated by this evaluation + * context. + *

This flag is orthogonal to whether compilation has been enabled via + * {@link org.springframework.expression.spel.SpelParserConfiguration} or the + * {@value org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME} + * system property. Both must permit compilation for a given expression to be compiled. + *

If this method returns {@code false}, the SpEL compiler will not compile + * expressions evaluated within this context, regardless of the compiler mode + * configured in the associated {@code SpelParserConfiguration} or system property. + * Furthermore, if a compiled form of the expression already exists, it will not be + * used; instead, interpreted evaluation will be performed. + *

By default, this method returns {@code true}. Concrete implementations may override + * this default method to indicate that compilation is not supported. + * @return {@code true} if compilation is supported; {@code false} otherwise + * @since 7.0.9 + * @see org.springframework.expression.spel.support.SimpleEvaluationContext.Builder#withCompilationSupported() + */ + default boolean isCompilationSupported() { + return true; + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java index 87133da0779..6664f17f358 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java @@ -75,14 +75,14 @@ public class SpelExpression implements Expression { private final SpelParserConfiguration configuration; - // The default context is used if no override is supplied by the user + // The default context is used if no override is supplied by the user. private @Nullable EvaluationContext evaluationContext; - // Holds the compiled form of the expression (if it has been compiled) + // Holds the compiled form of the expression (if it has been compiled). private volatile @Nullable CompiledExpression compiledAst; - // Count of many times as the expression been interpreted - can trigger compilation - // when certain limit reached + // Counts how many times the expression has been interpreted - can trigger compilation + // when a certain limit is reached. private final AtomicInteger interpretedCount = new AtomicInteger(); // The number of times compilation was attempted and failed - enables us to eventually @@ -129,10 +129,10 @@ public class SpelExpression implements Expression { @Override public @Nullable Object getValue() throws EvaluationException { + EvaluationContext context = getEvaluationContext(); CompiledExpression compiledAst = this.compiledAst; - if (compiledAst != null) { + if (compiledAst != null && context.isCompilationSupported()) { try { - EvaluationContext context = getEvaluationContext(); return compiledAst.getValue(context.getRootObject().getValue(), context); } catch (Throwable ex) { @@ -148,7 +148,7 @@ public class SpelExpression implements Expression { } } - ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); + ExpressionState expressionState = new ExpressionState(context, this.configuration); Object result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; @@ -157,17 +157,16 @@ public class SpelExpression implements Expression { @SuppressWarnings("unchecked") @Override public @Nullable T getValue(@Nullable Class expectedResultType) throws EvaluationException { + EvaluationContext context = getEvaluationContext(); CompiledExpression compiledAst = this.compiledAst; - if (compiledAst != null) { + if (compiledAst != null && context.isCompilationSupported()) { try { - EvaluationContext context = getEvaluationContext(); Object result = compiledAst.getValue(context.getRootObject().getValue(), context); if (expectedResultType == null) { return (T) result; } else { - return ExpressionUtils.convertTypedValue( - getEvaluationContext(), new TypedValue(result), expectedResultType); + return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType); } } catch (Throwable ex) { @@ -183,19 +182,19 @@ public class SpelExpression implements Expression { } } - ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); + ExpressionState expressionState = new ExpressionState(context, this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); - return ExpressionUtils.convertTypedValue( - expressionState.getEvaluationContext(), typedResultValue, expectedResultType); + return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); } @Override public @Nullable Object getValue(@Nullable Object rootObject) throws EvaluationException { + EvaluationContext context = getEvaluationContext(); CompiledExpression compiledAst = this.compiledAst; - if (compiledAst != null) { + if (compiledAst != null && context.isCompilationSupported()) { try { - return compiledAst.getValue(rootObject, getEvaluationContext()); + return compiledAst.getValue(rootObject, context); } catch (Throwable ex) { // If running in mixed mode, revert to interpreted @@ -211,7 +210,7 @@ public class SpelExpression implements Expression { } ExpressionState expressionState = - new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); + new ExpressionState(context, toTypedValue(rootObject), this.configuration); Object result = this.ast.getValue(expressionState); checkCompile(expressionState); return result; @@ -220,16 +219,16 @@ public class SpelExpression implements Expression { @SuppressWarnings("unchecked") @Override public @Nullable T getValue(@Nullable Object rootObject, @Nullable Class expectedResultType) throws EvaluationException { + EvaluationContext context = getEvaluationContext(); CompiledExpression compiledAst = this.compiledAst; - if (compiledAst != null) { + if (compiledAst != null && context.isCompilationSupported()) { try { - Object result = compiledAst.getValue(rootObject, getEvaluationContext()); + Object result = compiledAst.getValue(rootObject, context); if (expectedResultType == null) { - return (T)result; + return (T) result; } else { - return ExpressionUtils.convertTypedValue( - getEvaluationContext(), new TypedValue(result), expectedResultType); + return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType); } } catch (Throwable ex) { @@ -246,11 +245,10 @@ public class SpelExpression implements Expression { } ExpressionState expressionState = - new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration); + new ExpressionState(context, toTypedValue(rootObject), this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); checkCompile(expressionState); - return ExpressionUtils.convertTypedValue( - expressionState.getEvaluationContext(), typedResultValue, expectedResultType); + return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); } @Override @@ -258,7 +256,7 @@ public class SpelExpression implements Expression { Assert.notNull(context, "EvaluationContext must not be null"); CompiledExpression compiledAst = this.compiledAst; - if (compiledAst != null) { + if (compiledAst != null && context.isCompilationSupported()) { try { return compiledAst.getValue(context.getRootObject().getValue(), context); } @@ -287,7 +285,7 @@ public class SpelExpression implements Expression { Assert.notNull(context, "EvaluationContext must not be null"); CompiledExpression compiledAst = this.compiledAst; - if (compiledAst != null) { + if (compiledAst != null && context.isCompilationSupported()) { try { Object result = compiledAst.getValue(context.getRootObject().getValue(), context); if (expectedResultType != null) { @@ -321,7 +319,7 @@ public class SpelExpression implements Expression { Assert.notNull(context, "EvaluationContext must not be null"); CompiledExpression compiledAst = this.compiledAst; - if (compiledAst != null) { + if (compiledAst != null && context.isCompilationSupported()) { try { return compiledAst.getValue(rootObject, context); } @@ -352,7 +350,7 @@ public class SpelExpression implements Expression { Assert.notNull(context, "EvaluationContext must not be null"); CompiledExpression compiledAst = this.compiledAst; - if (compiledAst != null) { + if (compiledAst != null && context.isCompilationSupported()) { try { Object result = compiledAst.getValue(rootObject, context); if (expectedResultType != null) { @@ -480,6 +478,9 @@ public class SpelExpression implements Expression { */ private void checkCompile(ExpressionState expressionState) { this.interpretedCount.incrementAndGet(); + if (!expressionState.getEvaluationContext().isCompilationSupported()) { + return; + } SpelCompilerMode compilerMode = expressionState.getConfiguration().getCompilerMode(); if (compilerMode != SpelCompilerMode.OFF) { if (compilerMode == SpelCompilerMode.IMMEDIATE) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java index fed658a2fa4..486d01deccb 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java @@ -150,10 +150,12 @@ public final class SimpleEvaluationContext implements EvaluationContext { private final boolean assignmentEnabled; + private final boolean compilationSupported; + private SimpleEvaluationContext(List propertyAccessors, List indexAccessors, List resolvers, @Nullable TypeConverter converter, @Nullable TypedValue rootObject, - boolean assignmentEnabled) { + boolean assignmentEnabled, boolean compilationSupported) { this.propertyAccessors = propertyAccessors; this.indexAccessors = indexAccessors; @@ -161,6 +163,7 @@ public final class SimpleEvaluationContext implements EvaluationContext { this.typeConverter = (converter != null ? converter : new StandardTypeConverter()); this.rootObject = (rootObject != null ? rootObject : TypedValue.NULL); this.assignmentEnabled = assignmentEnabled; + this.compilationSupported = compilationSupported; } @@ -302,6 +305,19 @@ public final class SimpleEvaluationContext implements EvaluationContext { return this.assignmentEnabled; } + /** + * Determine if compilation is supported within expressions evaluated by this evaluation + * context. + *

By default, compilation is not supported in {@code SimpleEvaluationContext}. + * @return {@code true} if compilation is supported; {@code false} otherwise + * @since 7.0.9 + * @see Builder#withCompilationSupported() + */ + @Override + public boolean isCompilationSupported() { + return this.compilationSupported; + } + /** * Create a {@code SimpleEvaluationContext} for the specified {@link PropertyAccessor} * delegates: typically a custom {@code PropertyAccessor} specific to a use case — @@ -375,6 +391,8 @@ public final class SimpleEvaluationContext implements EvaluationContext { private boolean assignmentEnabled = true; + private boolean compilationSupported = false; + private Builder(PropertyAccessor... accessors) { this.propertyAccessors = Arrays.asList(accessors); @@ -391,6 +409,22 @@ public final class SimpleEvaluationContext implements EvaluationContext { return this; } + /** + * Indicate that compilation is supported within expressions evaluated by this + * evaluation context. + *

By default, compilation is not supported in {@code SimpleEvaluationContext}. + * Call this method to opt in to compilation — for example, when evaluating + * trusted expressions where performance is critical. + *

WARNING: Opting in to compilation for expressions from + * untrusted sources removes the safety guards supported in interpreted mode. + * @since 7.0.9 + * @see SimpleEvaluationContext#isCompilationSupported() + */ + public Builder withCompilationSupported() { + this.compilationSupported = true; + return this; + } + /** * Register the specified {@link IndexAccessor} delegates. * @param indexAccessors the index accessors to use @@ -480,7 +514,8 @@ public final class SimpleEvaluationContext implements EvaluationContext { public SimpleEvaluationContext build() { return new SimpleEvaluationContext(this.propertyAccessors, this.indexAccessors, - this.resolvers, this.typeConverter, this.rootObject, this.assignmentEnabled); + this.resolvers, this.typeConverter, this.rootObject, + this.assignmentEnabled, this.compilationSupported); } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java index 65f957d4a2a..67799dd7ab5 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java @@ -21,14 +21,18 @@ import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.springframework.core.Ordered; +import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.SpelCompilerMode; import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.support.SimpleEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.BOOLEAN; import static org.springframework.expression.spel.standard.SpelExpressionTestUtils.assertIsCompiled; +import static org.springframework.expression.spel.standard.SpelExpressionTestUtils.assertIsNotCompiled; +import static org.springframework.expression.spel.standard.SpelExpressionTestUtils.getInterpretedCount; /** * Tests for the {@link SpelCompiler}. @@ -50,6 +54,7 @@ class SpelCompilerTests { // Evaluate the expression multiple times to ensure that it gets compiled. IntStream.rangeClosed(1, 5).forEach(i -> assertThat(expression.getValue(component)).isEqualTo(42)); + assertIsCompiled(expression); } @Test // gh-25706 @@ -77,6 +82,162 @@ class SpelCompilerTests { assertThat(expression.getValue(context)).asInstanceOf(BOOLEAN).isTrue(); } + @Test + void simpleEvaluationContextBlocksCompilationByDefault() { + SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelExpressionParser parser = new SpelExpressionParser(config); + // "order" resides in the public Ordered interface and is therefore compilable, + // so any non-compilation is attributable solely to the context's policy. + Expression expression = parser.parseExpression("order"); + + SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + assertThat(context.isCompilationSupported()).isFalse(); + + // Evaluate the expression multiple times to ensure that it stays in interpreted mode, + // effectively overriding SpelCompilerMode.IMMEDIATE. + OrderedComponent component = new OrderedComponent(); + IntStream.rangeClosed(1, 5).forEach(i -> assertThat(expression.getValue(context, component)).isEqualTo(42)); + assertIsNotCompiled(expression); + } + + @Test + void simpleEvaluationContextAllowsCompilationWhenSupported() { + SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelExpressionParser parser = new SpelExpressionParser(config); + // "order" resides in the public Ordered interface and is therefore compilable. + Expression expression = parser.parseExpression("order"); + + SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding() + .withCompilationSupported() + .build(); + assertThat(context.isCompilationSupported()).isTrue(); + + // Two evaluations are enough for IMMEDIATE mode to compile. + OrderedComponent component = new OrderedComponent(); + IntStream.rangeClosed(1, 2).forEach(i -> assertThat(expression.getValue(context, component)).isEqualTo(42)); + assertIsCompiled(expression); + } + + @Test + void simpleEvaluationContextIgnoresPrecompiledExpressionByDefault() { + SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelExpressionParser parser = new SpelExpressionParser(config); + // "order" resides in the public Ordered interface and is therefore compilable. + Expression expression = parser.parseExpression("order"); + + EvaluationContext standardContext = new StandardEvaluationContext(); + assertThat(standardContext.isCompilationSupported()).isTrue(); + OrderedComponent component = new OrderedComponent(); + IntStream.rangeClosed(1, 2).forEach(i -> assertThat(expression.getValue(standardContext, component)).isEqualTo(42)); + assertIsCompiled(expression); + + // Switch to a SimpleEvaluationContext without opting into compilation — should + // fall back to interpreted evaluation even though compiledAst is non-null. + EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + assertThat(simpleContext.isCompilationSupported()).isFalse(); + + // Record interpretedCount before the simpleContext evaluation. + // checkCompile() — which increments interpretedCount as its very first action — + // is only reachable from the interpreted path. If the compiled path were taken + // instead, interpretedCount would not change. + int interpretedCountBefore = getInterpretedCount(expression); + assertThat(expression.getValue(simpleContext, component)).isEqualTo(42); + assertThat(getInterpretedCount(expression)).isEqualTo(interpretedCountBefore + 1); + + // compiledAst is still set: the compiled expression was not cleared, rather merely ignored. + assertIsCompiled(expression); + } + + /** + * Verify that the four implicit {@link EvaluationContext} {@code getValue()} variants + * in {@link SpelExpression} honor a {@link SimpleEvaluationContext} set as the default + * context: compilation must be blocked even under {@link SpelCompilerMode#IMMEDIATE}. + */ + @Test + void simpleEvaluationContextSetAsDefaultBlocksCompilationForImplicitContextVariants() { + SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelExpressionParser parser = new SpelExpressionParser(config); + // "order" resides in the public Ordered interface and is therefore compilable, + // so any non-compilation is attributable solely to the context's policy. + SpelExpression expression = parser.parseRaw("order"); + + OrderedComponent component = new OrderedComponent(); + SimpleEvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding() + .withRootObject(component) + .build(); + assertThat(simpleContext.isCompilationSupported()).isFalse(); + expression.setEvaluationContext(simpleContext); + + // Evaluate the expression multiple times using all four implicit context + // variants to ensure that they stay in interpreted mode. + for (int i = 0; i < 5; i++) { + assertThat(expression.getValue()).isEqualTo(42); + assertIsNotCompiled(expression); + + assertThat(expression.getValue(Integer.class)).isEqualTo(42); + assertIsNotCompiled(expression); + + assertThat(expression.getValue(component)).isEqualTo(42); + assertIsNotCompiled(expression); + + assertThat(expression.getValue(component, Integer.class)).isEqualTo(42); + assertIsNotCompiled(expression); + } + } + + /** + * Verify that the four implicit {@link EvaluationContext} {@code getValue()} variants + * in {@link SpelExpression} ignore a previously compiled expression when the default + * context is a {@link SimpleEvaluationContext} (where {@code isCompilationSupported()} + * returns {@code false}). + */ + @Test + void simpleEvaluationContextSetAsDefaultIgnoresPrecompiledExpressionForImplicitContextVariants() { + SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelExpressionParser parser = new SpelExpressionParser(config); + // "order" resides in the public Ordered interface and is therefore compilable. + SpelExpression expression = parser.parseRaw("order"); + + // Compile the expression via StandardEvaluationContext. + StandardEvaluationContext standardContext = new StandardEvaluationContext(); + assertThat(standardContext.isCompilationSupported()).isTrue(); + OrderedComponent component = new OrderedComponent(); + IntStream.rangeClosed(1, 2).forEach(i -> + assertThat(expression.getValue(standardContext, component, Integer.class)).isEqualTo(42)); + assertIsCompiled(expression); + + // Switch to a SimpleEvaluationContext set as the default context — the precompiled + // expression should be ignored for all four implicit context getValue() variants. + SimpleEvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding() + .withRootObject(component) + .build(); + assertThat(simpleContext.isCompilationSupported()).isFalse(); + expression.setEvaluationContext(simpleContext); + + // Record interpretedCount before the simpleContext evaluations. + // checkCompile() — which increments interpretedCount as its very first action — + // is only reachable from the interpreted path. If the compiled path were taken + // instead, interpretedCount would not change. + int interpretedCountBefore = getInterpretedCount(expression); + assertThat(expression.getValue()).isEqualTo(42); + assertThat(getInterpretedCount(expression)).isEqualTo(interpretedCountBefore + 1); + + interpretedCountBefore = getInterpretedCount(expression); + assertThat(expression.getValue(Integer.class)).isEqualTo(42); + assertThat(getInterpretedCount(expression)).isEqualTo(interpretedCountBefore + 1); + + interpretedCountBefore = getInterpretedCount(expression); + assertThat(expression.getValue(component)).isEqualTo(42); + assertThat(getInterpretedCount(expression)).isEqualTo(interpretedCountBefore + 1); + + interpretedCountBefore = getInterpretedCount(expression); + assertThat(expression.getValue(component, Integer.class)).isEqualTo(42); + assertThat(getInterpretedCount(expression)).isEqualTo(interpretedCountBefore + 1); + + // compiledAst is still set: the compiled expression was not cleared, rather merely ignored. + assertIsCompiled(expression); + } + @Test // gh-28043 void changingRegisteredVariableTypeDoesNotResultInFailureInMixedMode() { SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.MIXED, null); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelExpressionTestUtils.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelExpressionTestUtils.java index 124071379e3..2e55822f934 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelExpressionTestUtils.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelExpressionTestUtils.java @@ -17,6 +17,7 @@ package org.springframework.expression.spel.standard; import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicInteger; import org.springframework.expression.Expression; @@ -26,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests utilities for {@link SpelExpression}. * * @author Stephane Nicoll + * @author Sam Brannen */ public abstract class SpelExpressionTestUtils { @@ -41,4 +43,33 @@ public abstract class SpelExpressionTestUtils { } } + public static void assertIsNotCompiled(Expression expression) { + try { + Field field = SpelExpression.class.getDeclaredField("compiledAst"); + field.setAccessible(true); + Object object = field.get(expression); + assertThat(object).isNull(); + } + catch (Exception ex) { + throw new AssertionError(ex.getMessage(), ex); + } + } + + /** + * Return the current interpreted evaluation count for the given expression. + *

This counter is incremented exclusively by the interpreted evaluation path + * (inside {@code checkCompile()}), so it serves as a reliable witness for + * distinguishing interpreted from compiled evaluations in tests. + */ + public static int getInterpretedCount(Expression expression) { + try { + Field field = SpelExpression.class.getDeclaredField("interpretedCount"); + field.setAccessible(true); + return ((AtomicInteger) field.get(expression)).get(); + } + catch (Exception ex) { + throw new AssertionError(ex.getMessage(), ex); + } + } + } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/support/SimpleEvaluationContextTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/support/SimpleEvaluationContextTests.java index f3e5a2cfa3a..79f04df0c0f 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/support/SimpleEvaluationContextTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/support/SimpleEvaluationContextTests.java @@ -53,6 +53,24 @@ class SimpleEvaluationContextTests { private final Model model = new Model(); + @Test + void compilationNotSupportedByDefault() { + assertThat(SimpleEvaluationContext.forReadOnlyDataBinding().build().isCompilationSupported()).isFalse(); + assertThat(SimpleEvaluationContext.forReadWriteDataBinding().build().isCompilationSupported()).isFalse(); + assertThat(SimpleEvaluationContext.forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess()) + .build().isCompilationSupported()).isFalse(); + } + + @Test + void compilationSupportedViaBuilder() { + assertThat(SimpleEvaluationContext.forReadOnlyDataBinding() + .withCompilationSupported().build().isCompilationSupported()).isTrue(); + assertThat(SimpleEvaluationContext.forReadWriteDataBinding() + .withCompilationSupported().build().isCompilationSupported()).isTrue(); + assertThat(SimpleEvaluationContext.forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess()) + .withCompilationSupported().build().isCompilationSupported()).isTrue(); + } + @Test void forReadWriteDataBinding() { SimpleEvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding()