Skip to content

Commit

Permalink
Fix SpEL support for quotes within String literals
Browse files Browse the repository at this point in the history
Prior to this commit, there were two bugs in the support for quotes
within String literals in SpEL expressions.

- Two double quotes ("") or two single quotes ('') were always replaced
  with one double quote or one single quote, respectively, regardless
  of which quote character was used to enclose the original String
  literal. This resulted in the loss of one of the double quotes when
  the String literal was enclosed in single quotes, and vice versa. For
  example, 'x "" y' became 'x " y'.

- A single quote which was properly escaped in a String literal
  enclosed within single quotes was not escaped in the AST string
  representation of the expression. For example, 'x '' y' became 'x ' y'.

This commit fixes both of these related issues in StringLiteral and
overhauls the structure of ParsingTests.

Closes spring-projectsgh-29604, spring-projectsgh-28356
  • Loading branch information
sbrannen authored and mdeinum committed Jun 29, 2023
1 parent 965a001 commit 2473e9e
Show file tree
Hide file tree
Showing 4 changed files with 552 additions and 410 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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 All @@ -26,6 +26,7 @@
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public class StringLiteral extends Literal {
Expand All @@ -36,9 +37,19 @@ public class StringLiteral extends Literal {
public StringLiteral(String payload, int startPos, int endPos, String value) {
super(payload, startPos, endPos);

// The original enclosing quote character for the string literal: ' or ".
char quoteCharacter = value.charAt(0);

// Remove enclosing quotes
String valueWithinQuotes = value.substring(1, value.length() - 1);
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");

// Replace escaped internal quote characters
if (quoteCharacter == '\'') {
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
}
else {
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
}

this.value = new TypedValue(valueWithinQuotes);
this.exitTypeDescriptor = "Ljava/lang/String";
Expand All @@ -52,7 +63,9 @@ public TypedValue getLiteralValue() {

@Override
public String toString() {
return "'" + getLiteralValue().getValue() + "'";
String ast = String.valueOf(getLiteralValue().getValue());
ast = StringUtils.replace(ast, "'", "''");
return "'" + ast + "'";
}

@Override
Expand Down
Expand Up @@ -364,6 +364,55 @@ void limitCollectionGrowing() {

}

@Nested
class StringLiterals {

@Test
void insideSingleQuotes() {
evaluate("'hello'", "hello", String.class);
evaluate("'hello world'", "hello world", String.class);
}

@Test
void insideDoubleQuotes() {
evaluate("\"hello\"", "hello", String.class);
evaluate("\"hello world\"", "hello world", String.class);
}

@Test
void singleQuotesInsideSingleQuotes() {
evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class);
evaluate("'big ''''pizza'''' parlor'", "big ''pizza'' parlor", String.class);
}

@Test
void doubleQuotesInsideDoubleQuotes() {
evaluate("\"big \"\"pizza\"\" parlor\"", "big \"pizza\" parlor", String.class);
evaluate("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "big \"\"pizza\"\" parlor", String.class);
}

@Test
void singleQuotesInsideDoubleQuotes() {
evaluate("\"Tony's Pizza\"", "Tony's Pizza", String.class);
evaluate("\"big ''pizza'' parlor\"", "big ''pizza'' parlor", String.class);
}

@Test
void doubleQuotesInsideSingleQuotes() {
evaluate("'big \"pizza\" parlor'", "big \"pizza\" parlor", String.class);
evaluate("'two double \"\" quotes'", "two double \"\" quotes", String.class);
}

@Test
void inCompoundExpressions() {
evaluate("'123''4' == '123''4'", true, Boolean.class);
evaluate("""
"123""4" == "123""4"\
""", true, Boolean.class);
}

}

@Nested
class RelationalOperatorTests {

Expand Down

0 comments on commit 2473e9e

Please sign in to comment.