summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tclListObj.c19
-rw-r--r--generic/tclUtil.c17
-rw-r--r--tests/list.test18
3 files changed, 53 insertions, 1 deletions
diff --git a/generic/tclListObj.c b/generic/tclListObj.c
index 786e1ce..e42567e 100644
--- a/generic/tclListObj.c
+++ b/generic/tclListObj.c
@@ -1990,7 +1990,21 @@ UpdateStringOfList(
* Pass 2: copy into string rep buffer.
*/
+ /*
+ * We used to set the string length here, relying on a presumed
+ * guarantee that the number of bytes TclScanElement() calls reported
+ * to be needed was a precise count and not an over-estimate, so long
+ * as the same flag values were passed to TclConvertElement().
+ *
+ * Then we saw [35a8f1c04a], where a bug in TclScanElement() caused
+ * that guarantee to fail. Rather than trust there are no more bugs,
+ * we set the length after the loop based on what was actually written,
+ * an not on what was predicted.
+ *
listPtr->length = bytesNeeded - 1;
+ *
+ */
+
listPtr->bytes = ckalloc(bytesNeeded);
dst = listPtr->bytes;
for (i = 0; i < numElems; i++) {
@@ -1999,7 +2013,10 @@ UpdateStringOfList(
dst += TclConvertElement(elem, length, dst, flagPtr[i]);
*dst++ = ' ';
}
- listPtr->bytes[listPtr->length] = '\0';
+ dst[-1] = '\0';
+
+ /* Here is the safe setting of the string length. */
+ listPtr->length = dst - 1 - listPtr->bytes;
if (flagPtr != localFlags) {
ckfree(flagPtr);
diff --git a/generic/tclUtil.c b/generic/tclUtil.c
index 6eab7b8..d5cc7c2 100644
--- a/generic/tclUtil.c
+++ b/generic/tclUtil.c
@@ -1046,6 +1046,23 @@ TclScanElement(
return 2;
}
+#if COMPAT
+ /*
+ * We have an established history in TclConvertElement() when quoting
+ * because of a leading hash character to force what would be the
+ * CONVERT_MASK mode into the CONVERT_BRACE mode. That is, we format
+ * the element #{a"b} like this:
+ * {#{a"b}}
+ * and not like this:
+ * \#{a\"b}
+ * This is inconsistent with [list x{a"b}], but we will not change that now.
+ * Set that preference here so that we compute a tight size requirement.
+ */
+ if ((*src == '#') && !(*flagPtr & TCL_DONT_QUOTE_HASH)) {
+ preferBrace = 1;
+ }
+#endif
+
if ((*p == '{') || (*p == '"')) {
/*
* Must escape or protect so leading character of value is not
diff --git a/tests/list.test b/tests/list.test
index dff5d50..2686bd7 100644
--- a/tests/list.test
+++ b/tests/list.test
@@ -128,6 +128,24 @@ test list-3.1 {SetListFromAny and lrange/concat results} {
test list-4.1 {Bug 3173086} {
string is list "{[list \\\\\}]}"
} 1
+test list-4.2 {Bug 35a8f1c04a, check correct str-rep} {
+ set result {}
+ foreach i {
+ {#"} {#"""} {#"""""""""""""""}
+ "#\"{" "#\"\"\"{" "#\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\{"
+ "#\"}" "#\"\"\"}" "#\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\}"
+ } {
+ set list [list $i]
+ set list [string trim " $list "]
+ if {[llength $list] > 1 || $i ne [lindex $list 0]} {
+ lappend result "wrong string-representation of list by '$i', length: [llength $list], list: '$list'"
+ }
+ }
+ set result [join $result \n]
+} {}
+test list-4.3 {Bug 35a8f1c04a, check correct string length} {
+ string length [list #""]
+} 5
# cleanup
::tcltest::cleanupTests