8291598: Matcher.appendReplacement should not create new StringBuilder instances

Reviewed-by: rriggs
This commit is contained in:
Raffaello Giulietti 2023-03-28 17:55:23 +00:00
parent 1683a63a7d
commit ca745cb426

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,6 +25,7 @@
package java.util.regex; package java.util.regex;
import java.io.IOException;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
@ -917,12 +918,16 @@ public final class Matcher implements MatchResult {
*/ */
public Matcher appendReplacement(StringBuffer sb, String replacement) { public Matcher appendReplacement(StringBuffer sb, String replacement) {
checkMatch(); checkMatch();
StringBuilder result = new StringBuilder(); int curLen = sb.length();
appendExpandedReplacement(replacement, result); try {
// Append the intervening text // Append the intervening text
sb.append(text, lastAppendPosition, first); sb.append(text, lastAppendPosition, first);
// Append the match substitution // Append the match substitution
sb.append(result); appendExpandedReplacement(sb, replacement);
} catch (IllegalArgumentException e) {
sb.setLength(curLen);
throw e;
}
lastAppendPosition = last; lastAppendPosition = last;
modCount++; modCount++;
return this; return this;
@ -1004,14 +1009,17 @@ public final class Matcher implements MatchResult {
* @since 9 * @since 9
*/ */
public Matcher appendReplacement(StringBuilder sb, String replacement) { public Matcher appendReplacement(StringBuilder sb, String replacement) {
// If no match, return error
checkMatch(); checkMatch();
StringBuilder result = new StringBuilder(); int curLen = sb.length();
appendExpandedReplacement(replacement, result); try {
// Append the intervening text // Append the intervening text
sb.append(text, lastAppendPosition, first); sb.append(text, lastAppendPosition, first);
// Append the match substitution // Append the match substitution
sb.append(result); appendExpandedReplacement(sb, replacement);
} catch (IllegalArgumentException e) {
sb.setLength(curLen);
throw e;
}
lastAppendPosition = last; lastAppendPosition = last;
modCount++; modCount++;
return this; return this;
@ -1021,93 +1029,94 @@ public final class Matcher implements MatchResult {
* Processes replacement string to replace group references with * Processes replacement string to replace group references with
* groups. * groups.
*/ */
private StringBuilder appendExpandedReplacement( private void appendExpandedReplacement(Appendable app, String replacement) {
String replacement, StringBuilder result) { try {
int cursor = 0; int cursor = 0;
while (cursor < replacement.length()) { while (cursor < replacement.length()) {
char nextChar = replacement.charAt(cursor); char nextChar = replacement.charAt(cursor);
if (nextChar == '\\') { if (nextChar == '\\') {
cursor++;
if (cursor == replacement.length())
throw new IllegalArgumentException(
"character to be escaped is missing");
nextChar = replacement.charAt(cursor);
result.append(nextChar);
cursor++;
} else if (nextChar == '$') {
// Skip past $
cursor++;
// Throw IAE if this "$" is the last character in replacement
if (cursor == replacement.length())
throw new IllegalArgumentException(
"Illegal group reference: group index is missing");
nextChar = replacement.charAt(cursor);
int refNum = -1;
if (nextChar == '{') {
cursor++; cursor++;
StringBuilder gsb = new StringBuilder(); if (cursor == replacement.length())
while (cursor < replacement.length()) { throw new IllegalArgumentException(
nextChar = replacement.charAt(cursor); "character to be escaped is missing");
if (ASCII.isLower(nextChar) || nextChar = replacement.charAt(cursor);
ASCII.isUpper(nextChar) || app.append(nextChar);
ASCII.isDigit(nextChar)) { cursor++;
gsb.append(nextChar); } else if (nextChar == '$') {
cursor++; // Skip past $
} else { cursor++;
break; // Throw IAE if this "$" is the last character in replacement
if (cursor == replacement.length())
throw new IllegalArgumentException(
"Illegal group reference: group index is missing");
nextChar = replacement.charAt(cursor);
int refNum = -1;
if (nextChar == '{') {
cursor++;
int begin = cursor;
while (cursor < replacement.length()) {
nextChar = replacement.charAt(cursor);
if (ASCII.isLower(nextChar) ||
ASCII.isUpper(nextChar) ||
ASCII.isDigit(nextChar)) {
cursor++;
} else {
break;
}
}
if (begin == cursor)
throw new IllegalArgumentException(
"named capturing group has 0 length name");
if (nextChar != '}')
throw new IllegalArgumentException(
"named capturing group is missing trailing '}'");
String gname = replacement.substring(begin, cursor);
if (ASCII.isDigit(gname.charAt(0)))
throw new IllegalArgumentException(
"capturing group name {" + gname +
"} starts with digit character");
if (!namedGroups().containsKey(gname))
throw new IllegalArgumentException(
"No group with name {" + gname + "}");
refNum = namedGroups().get(gname);
cursor++;
} else {
// The first number is always a group
refNum = nextChar - '0';
if ((refNum < 0) || (refNum > 9))
throw new IllegalArgumentException(
"Illegal group reference");
cursor++;
// Capture the largest legal group string
boolean done = false;
while (!done) {
if (cursor >= replacement.length()) {
break;
}
int nextDigit = replacement.charAt(cursor) - '0';
if ((nextDigit < 0) || (nextDigit > 9)) { // not a number
break;
}
int newRefNum = (refNum * 10) + nextDigit;
if (groupCount() < newRefNum) {
done = true;
} else {
refNum = newRefNum;
cursor++;
}
} }
} }
if (gsb.length() == 0) // Append group
throw new IllegalArgumentException( if (start(refNum) != -1 && end(refNum) != -1)
"named capturing group has 0 length name"); app.append(text, start(refNum), end(refNum));
if (nextChar != '}')
throw new IllegalArgumentException(
"named capturing group is missing trailing '}'");
String gname = gsb.toString();
if (ASCII.isDigit(gname.charAt(0)))
throw new IllegalArgumentException(
"capturing group name {" + gname +
"} starts with digit character");
if (!namedGroups().containsKey(gname))
throw new IllegalArgumentException(
"No group with name {" + gname + "}");
refNum = namedGroups().get(gname);
cursor++;
} else { } else {
// The first number is always a group app.append(nextChar);
refNum = nextChar - '0';
if ((refNum < 0) || (refNum > 9))
throw new IllegalArgumentException(
"Illegal group reference");
cursor++; cursor++;
// Capture the largest legal group string
boolean done = false;
while (!done) {
if (cursor >= replacement.length()) {
break;
}
int nextDigit = replacement.charAt(cursor) - '0';
if ((nextDigit < 0) || (nextDigit > 9)) { // not a number
break;
}
int newRefNum = (refNum * 10) + nextDigit;
if (groupCount() < newRefNum) {
done = true;
} else {
refNum = newRefNum;
cursor++;
}
}
} }
// Append group
if (start(refNum) != -1 && end(refNum) != -1)
result.append(text, start(refNum), end(refNum));
} else {
result.append(nextChar);
cursor++;
} }
} catch (IOException e) { // cannot happen on String[Buffer|Builder]
throw new AssertionError(e.getMessage());
} }
return result;
} }
/** /**