diff --git a/src/main/java/org/apache/commons/text/StringSubstitutor.java b/src/main/java/org/apache/commons/text/StringSubstitutor.java index f4b53eec17..01fc5510d5 100644 --- a/src/main/java/org/apache/commons/text/StringSubstitutor.java +++ b/src/main/java/org/apache/commons/text/StringSubstitutor.java @@ -1408,7 +1408,7 @@ private int substitute(final TextStringBuilder buf, final int offset, final int // on the first call initialize priorVariables if (priorVariables == null) { priorVariables = new ArrayList<>(); - priorVariables.add(new String(chars, offset, length)); + priorVariables.add(new String(chars, offset, length + lengthChange)); } // handle cyclic substitution diff --git a/src/test/java/org/apache/commons/text/StringSubstitutorTest.java b/src/test/java/org/apache/commons/text/StringSubstitutorTest.java index b4390d808b..3705cea3a9 100644 --- a/src/test/java/org/apache/commons/text/StringSubstitutorTest.java +++ b/src/test/java/org/apache/commons/text/StringSubstitutorTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; @@ -541,6 +542,30 @@ public void testReplaceToIdentical() { doTestReplace("The ${animal} jumps.", "The ${animal} jumps.", true); } + /** + * Tests a cyclic replace operation. + * The cycle should be detected and cause an exception to be thrown. + */ + @Test + public void testCyclicReplacement() { + final Map map = new HashMap<>(); + map.put("animal", "${critter}"); + map.put("target", "${pet}"); + map.put("pet", "${petCharacteristic} dog"); + map.put("petCharacteristic", "lazy"); + map.put("critter", "${critterSpeed} ${critterColor} ${critterType}"); + map.put("critterSpeed", "quick"); + map.put("critterColor", "brown"); + map.put("critterType", "${animal}"); + final StringSubstitutor sub = new StringSubstitutor(map); + assertThrows(IllegalStateException.class, () -> sub.replace("The ${animal} jumps over the ${target}.")); + + // also check even when default value is set. + map.put("critterType", "${animal:-fox}"); + assertThrows(IllegalStateException.class, + () -> new StringSubstitutor(map).replace("The ${animal} jumps over the ${target}.")); + } + /** * Tests unknown key replace. */