Skip to content

Commit ca745cb

Browse files
committed
8291598: Matcher.appendReplacement should not create new StringBuilder instances
Reviewed-by: rriggs
1 parent 1683a63 commit ca745cb

File tree

1 file changed

+100
-91
lines changed

1 file changed

+100
-91
lines changed

src/java.base/share/classes/java/util/regex/Matcher.java

Lines changed: 100 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,7 @@
2525

2626
package java.util.regex;
2727

28+
import java.io.IOException;
2829
import java.util.ConcurrentModificationException;
2930
import java.util.Iterator;
3031
import java.util.Map;
@@ -917,12 +918,16 @@ public static String quoteReplacement(String s) {
917918
*/
918919
public Matcher appendReplacement(StringBuffer sb, String replacement) {
919920
checkMatch();
920-
StringBuilder result = new StringBuilder();
921-
appendExpandedReplacement(replacement, result);
922-
// Append the intervening text
923-
sb.append(text, lastAppendPosition, first);
924-
// Append the match substitution
925-
sb.append(result);
921+
int curLen = sb.length();
922+
try {
923+
// Append the intervening text
924+
sb.append(text, lastAppendPosition, first);
925+
// Append the match substitution
926+
appendExpandedReplacement(sb, replacement);
927+
} catch (IllegalArgumentException e) {
928+
sb.setLength(curLen);
929+
throw e;
930+
}
926931
lastAppendPosition = last;
927932
modCount++;
928933
return this;
@@ -1004,14 +1009,17 @@ public Matcher appendReplacement(StringBuffer sb, String replacement) {
10041009
* @since 9
10051010
*/
10061011
public Matcher appendReplacement(StringBuilder sb, String replacement) {
1007-
// If no match, return error
10081012
checkMatch();
1009-
StringBuilder result = new StringBuilder();
1010-
appendExpandedReplacement(replacement, result);
1011-
// Append the intervening text
1012-
sb.append(text, lastAppendPosition, first);
1013-
// Append the match substitution
1014-
sb.append(result);
1013+
int curLen = sb.length();
1014+
try {
1015+
// Append the intervening text
1016+
sb.append(text, lastAppendPosition, first);
1017+
// Append the match substitution
1018+
appendExpandedReplacement(sb, replacement);
1019+
} catch (IllegalArgumentException e) {
1020+
sb.setLength(curLen);
1021+
throw e;
1022+
}
10151023
lastAppendPosition = last;
10161024
modCount++;
10171025
return this;
@@ -1021,93 +1029,94 @@ public Matcher appendReplacement(StringBuilder sb, String replacement) {
10211029
* Processes replacement string to replace group references with
10221030
* groups.
10231031
*/
1024-
private StringBuilder appendExpandedReplacement(
1025-
String replacement, StringBuilder result) {
1026-
int cursor = 0;
1027-
while (cursor < replacement.length()) {
1028-
char nextChar = replacement.charAt(cursor);
1029-
if (nextChar == '\\') {
1030-
cursor++;
1031-
if (cursor == replacement.length())
1032-
throw new IllegalArgumentException(
1033-
"character to be escaped is missing");
1034-
nextChar = replacement.charAt(cursor);
1035-
result.append(nextChar);
1036-
cursor++;
1037-
} else if (nextChar == '$') {
1038-
// Skip past $
1039-
cursor++;
1040-
// Throw IAE if this "$" is the last character in replacement
1041-
if (cursor == replacement.length())
1042-
throw new IllegalArgumentException(
1043-
"Illegal group reference: group index is missing");
1044-
nextChar = replacement.charAt(cursor);
1045-
int refNum = -1;
1046-
if (nextChar == '{') {
1032+
private void appendExpandedReplacement(Appendable app, String replacement) {
1033+
try {
1034+
int cursor = 0;
1035+
while (cursor < replacement.length()) {
1036+
char nextChar = replacement.charAt(cursor);
1037+
if (nextChar == '\\') {
10471038
cursor++;
1048-
StringBuilder gsb = new StringBuilder();
1049-
while (cursor < replacement.length()) {
1050-
nextChar = replacement.charAt(cursor);
1051-
if (ASCII.isLower(nextChar) ||
1052-
ASCII.isUpper(nextChar) ||
1053-
ASCII.isDigit(nextChar)) {
1054-
gsb.append(nextChar);
1055-
cursor++;
1056-
} else {
1057-
break;
1058-
}
1059-
}
1060-
if (gsb.length() == 0)
1061-
throw new IllegalArgumentException(
1062-
"named capturing group has 0 length name");
1063-
if (nextChar != '}')
1064-
throw new IllegalArgumentException(
1065-
"named capturing group is missing trailing '}'");
1066-
String gname = gsb.toString();
1067-
if (ASCII.isDigit(gname.charAt(0)))
1068-
throw new IllegalArgumentException(
1069-
"capturing group name {" + gname +
1070-
"} starts with digit character");
1071-
if (!namedGroups().containsKey(gname))
1039+
if (cursor == replacement.length())
10721040
throw new IllegalArgumentException(
1073-
"No group with name {" + gname + "}");
1074-
refNum = namedGroups().get(gname);
1041+
"character to be escaped is missing");
1042+
nextChar = replacement.charAt(cursor);
1043+
app.append(nextChar);
10751044
cursor++;
1076-
} else {
1077-
// The first number is always a group
1078-
refNum = nextChar - '0';
1079-
if ((refNum < 0) || (refNum > 9))
1080-
throw new IllegalArgumentException(
1081-
"Illegal group reference");
1045+
} else if (nextChar == '$') {
1046+
// Skip past $
10821047
cursor++;
1083-
// Capture the largest legal group string
1084-
boolean done = false;
1085-
while (!done) {
1086-
if (cursor >= replacement.length()) {
1087-
break;
1088-
}
1089-
int nextDigit = replacement.charAt(cursor) - '0';
1090-
if ((nextDigit < 0) || (nextDigit > 9)) { // not a number
1091-
break;
1048+
// Throw IAE if this "$" is the last character in replacement
1049+
if (cursor == replacement.length())
1050+
throw new IllegalArgumentException(
1051+
"Illegal group reference: group index is missing");
1052+
nextChar = replacement.charAt(cursor);
1053+
int refNum = -1;
1054+
if (nextChar == '{') {
1055+
cursor++;
1056+
int begin = cursor;
1057+
while (cursor < replacement.length()) {
1058+
nextChar = replacement.charAt(cursor);
1059+
if (ASCII.isLower(nextChar) ||
1060+
ASCII.isUpper(nextChar) ||
1061+
ASCII.isDigit(nextChar)) {
1062+
cursor++;
1063+
} else {
1064+
break;
1065+
}
10921066
}
1093-
int newRefNum = (refNum * 10) + nextDigit;
1094-
if (groupCount() < newRefNum) {
1095-
done = true;
1096-
} else {
1097-
refNum = newRefNum;
1098-
cursor++;
1067+
if (begin == cursor)
1068+
throw new IllegalArgumentException(
1069+
"named capturing group has 0 length name");
1070+
if (nextChar != '}')
1071+
throw new IllegalArgumentException(
1072+
"named capturing group is missing trailing '}'");
1073+
String gname = replacement.substring(begin, cursor);
1074+
if (ASCII.isDigit(gname.charAt(0)))
1075+
throw new IllegalArgumentException(
1076+
"capturing group name {" + gname +
1077+
"} starts with digit character");
1078+
if (!namedGroups().containsKey(gname))
1079+
throw new IllegalArgumentException(
1080+
"No group with name {" + gname + "}");
1081+
refNum = namedGroups().get(gname);
1082+
cursor++;
1083+
} else {
1084+
// The first number is always a group
1085+
refNum = nextChar - '0';
1086+
if ((refNum < 0) || (refNum > 9))
1087+
throw new IllegalArgumentException(
1088+
"Illegal group reference");
1089+
cursor++;
1090+
// Capture the largest legal group string
1091+
boolean done = false;
1092+
while (!done) {
1093+
if (cursor >= replacement.length()) {
1094+
break;
1095+
}
1096+
int nextDigit = replacement.charAt(cursor) - '0';
1097+
if ((nextDigit < 0) || (nextDigit > 9)) { // not a number
1098+
break;
1099+
}
1100+
int newRefNum = (refNum * 10) + nextDigit;
1101+
if (groupCount() < newRefNum) {
1102+
done = true;
1103+
} else {
1104+
refNum = newRefNum;
1105+
cursor++;
1106+
}
10991107
}
11001108
}
1109+
// Append group
1110+
if (start(refNum) != -1 && end(refNum) != -1)
1111+
app.append(text, start(refNum), end(refNum));
1112+
} else {
1113+
app.append(nextChar);
1114+
cursor++;
11011115
}
1102-
// Append group
1103-
if (start(refNum) != -1 && end(refNum) != -1)
1104-
result.append(text, start(refNum), end(refNum));
1105-
} else {
1106-
result.append(nextChar);
1107-
cursor++;
11081116
}
1117+
} catch (IOException e) { // cannot happen on String[Buffer|Builder]
1118+
throw new AssertionError(e.getMessage());
11091119
}
1110-
return result;
11111120
}
11121121

11131122
/**

0 commit comments

Comments
 (0)