From 29bd0f42bf63e28d9a71adef02af67b319144576 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= <yoann@hibernate.org>
Date: Mon, 24 Feb 2020 17:58:41 +0100
Subject: [PATCH] HV-1774 Do not interpret '$\A{1+1}' in message templates

---
 .../messageinterpolation/parser/ELState.java  |  6 +++++-
 .../TokenCollectorTest.java                   | 21 ++++++++++++++++++-
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/messageinterpolation/parser/ELState.java b/engine/src/main/java/org/hibernate/validator/internal/engine/messageinterpolation/parser/ELState.java
index 9460fae05..fda95a153 100644
--- a/engine/src/main/java/org/hibernate/validator/internal/engine/messageinterpolation/parser/ELState.java
+++ b/engine/src/main/java/org/hibernate/validator/internal/engine/messageinterpolation/parser/ELState.java
@@ -51,7 +51,11 @@ public void handleEndTerm(char character, TokenCollector tokenCollector) throws
 	@Override
 	public void handleEscapeCharacter(char character, TokenCollector tokenCollector)
 			throws MessageDescriptorFormatException {
-		tokenCollector.transitionState( new EscapedState( this ) );
+		tokenCollector.appendToToken( TokenCollector.EL_DESIGNATOR );
+		tokenCollector.appendToToken( character );
+		// Do not go back to this state after the escape: $\ is not the start of an EL expression
+		ParserState stateAfterEscape = new MessageState();
+		tokenCollector.transitionState( new EscapedState( stateAfterEscape ) );
 	}
 
 	@Override
diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorTest.java
index 972a9e051..ab9299f17 100644
--- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorTest.java
+++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorTest.java
@@ -8,7 +8,11 @@
 
 import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTermType;
 import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.Token;
 import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector;
+
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.ListAssert;
 import org.testng.annotations.Test;
 
 /**
@@ -29,10 +33,25 @@ public void testParameterWithoutOpeningBraceThrowsException() throws Exception {
 	}
 
 	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
-	public void testELExpressionWithoutOpeningBraceThrowsException() throws Exception {
+	public void testELExpressionDollarThenClosingBraceThrowsException() throws Exception {
 		new TokenCollector( "$}", InterpolationTermType.EL );
 	}
 
+	@Test
+	public void testELExpressionDollarThenEscapeInterpretedAsLiterals() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "$\\A{1+1}", InterpolationTermType.EL )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "$\\A", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "{1+1}", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
 	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
 	public void testTermWithoutClosingBraceThrowsException() throws Exception {
 		new TokenCollector( "{foo", InterpolationTermType.PARAMETER );
