Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LANG-1753] StringUtils.replaceEachRepeatedly regression in 3.11+ #1297

Conversation

ParanoidUser
Copy link
Contributor

https://issues.apache.org/jira/browse/LANG-1753

The method replaceEachRepeatedly calls replaceEach repeatedly until no more replacements are possible or timeToLive < 0. The timeToLive starts with the value of searchList.length. The current implementation can't distinguish between infinite loops and deeply nested string patterns. In both cases, it continues replacements until it hits the time-to-live limit and throws IllegalStateException if replacements are still possible.

The problem with LANG-1528 fix is that in some cases, it allows partially replaced strings after TTL is exceeded. This fix reverts replaceEach logic back to version 3.10 (see PR #505), but allows deeper dives due to an increased initial TTL value. The initial TTL value is now set to the greater of searchList.length or up to 5 levels deep. This default value was chosen based on rational considerations.

@@ -6611,7 +6608,8 @@ private static String replaceEach(
* @since 2.4
*/
public static String replaceEachRepeatedly(final String text, final String[] searchList, final String[] replacementList) {
return replaceEach(text, searchList, replacementList, true, ArrayUtils.getLength(searchList));
int timeToLive = Math.max(ArrayUtils.getLength(searchList), DEFAULT_TTL);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Javadoc only mentions endless loops ("if the search is repeating and there is an endless loop"). It would be clearer if it also included a mention of the TTL to avoid confusion.

Comment on lines +1999 to +2000
assertEquals("a", StringUtils.replaceEachRepeatedly("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
new String[]{"aa"}, new String[]{"a"}));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest adding an example of IllegalStateException with a shorter input, like:

assertThrows(
	IllegalStateException.class,
	() -> StringUtils.replaceEachRepeatedly("aaaaaab", new String[] {"ab"}, new String[] {"b"}),
	"Should be a circular reference");

It might surprise newcomers that even far fewer than 32 repetitions can trigger an IllegalStateException.

@palacsint
Copy link

Thanks for the fix! Seems perfect for us.

@garydgregory garydgregory merged commit 972aa7b into apache:master Oct 15, 2024
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants