commons-text icon indicating copy to clipboard operation
commons-text copied to clipboard

Fix length change omission for StringSubstitutor cyclic detection

Open nickbabcock opened this issue 6 years ago • 4 comments

StringSubstitutor#substitute would throw StringIndexOutOfBoundsException when removing a variable start marker if StringSubstitutor wasn't using the internal representation of TextStringBuilder#buffer. The local variable, priorVariables, contains unbalanced squirrely parentheses (replacing $${${TEST}} would cause priorVariables to add ${${TEST}}} -- amusingly didn't result in unexpected behavior). I discovered this when swapping all instances of buf.buffer to buf.toCharArray(). This PR ensures that both now have equivalent behavior. I was unable to tease out a test for this, as the length is only decremented to that point (but this change in length goes unused, hence the exception for toCharArray), so no overflows. I did port a test from StrSubstitutorTest.java, but the test passes before and after this change.

I believe this to be a trivial change (hence no JIRA ticket). Let me know if otherwise

nickbabcock avatar Jun 12 '19 22:06 nickbabcock

Coverage Status

Coverage increased (+0.1%) to 98.031% when pulling e291fb56ffa30536a8e9abf1fa471d4da3f4fd30 on nickbabcock:sub-cycle-fix into 8b07e17402a7ca148aa6442c1c1e25a2f700c713 on apache:master.

coveralls avatar Jun 12 '19 23:06 coveralls

My apologies!

  1. Checkout master branch
  2. Apply diff
diff --git a/src/main/java/org/apache/commons/text/StringSubstitutor.java b/src/main/java/org/apache/commons/text/StringSubstitutor.java
index f4b53ee..4680edc 100644
--- a/src/main/java/org/apache/commons/text/StringSubstitutor.java
+++ b/src/main/java/org/apache/commons/text/StringSubstitutor.java
@@ -1330,7 +1330,7 @@ public class StringSubstitutor {
         final boolean top = priorVariables == null;
         boolean altered = false;
         int lengthChange = 0;
-        char[] chars = buf.buffer;
+        char[] chars = buf.toCharArray();
         int bufEnd = offset + length;
         int pos = offset;
         while (pos < bufEnd) {
@@ -1346,7 +1346,7 @@ public class StringSubstitutor {
                         continue;
                     }
                     buf.deleteCharAt(pos - 1);
-                    chars = buf.buffer; // in case buffer was altered
+                    chars = buf.toCharArray(); // in case buffer was altered
                     lengthChange--;
                     altered = true;
                     bufEnd--;
@@ -1432,7 +1432,7 @@ public class StringSubstitutor {
                                     pos += change;
                                     bufEnd += change;
                                     lengthChange += change;
-                                    chars = buf.buffer; // in case buffer was altered
+                                    chars = buf.toCharArray(); // in case buffer was altered
                                 } else if (undefinedVariableException) {
                                     throw new IllegalArgumentException(String.format(
                                             "Cannot resolve variable '%s' (enableSubstitutionInVariables=%s).", varName,
  1. Run tests
  2. Notice StringIndexOutOfBoundsException is thrown at
if (priorVariables == null) {
    priorVariables = new ArrayList<>();
    priorVariables.add(new String(chars, offset, length));
}
  1. Understand "length" points passed the character array but since chars is implicitly relying on TextStringBuilder allocating behavior so the current implementation does not throw exception.
  2. The real length is length + lengthChange

nickbabcock avatar Jun 13 '19 02:06 nickbabcock

I think this would still require a change, as it appears to prevent an error you had (and which users may have as well). Trivial changes are more javadocs, style changes, rename a local variable, fix unused imports, etc. Anything that doesn't affect users regarding backward compatibility (binary or behaviour wise), and also don't add any new feature.

It's a logic bug that can't manifest in commons-text is due to the reliance on internal TextStringBuilder#buffer and how it's implemented. So the number of users effected by this bug is 0. I only came across the bug when a I needed to apply some changes and created the diff posted above.

Let me know if you still want me to create a JIRA ticket 😄

nickbabcock avatar Jun 27 '19 02:06 nickbabcock

I would like feedback from the community on whether or not this should be merged. A failing test would make this a no-brainer of course but I understand this might not be possible here.

garydgregory avatar Jun 27 '19 18:06 garydgregory