From 9e0083e9f07d37493c802ab11661736fa680e12c Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Tue, 3 Dec 2019 12:27:42 +0000 Subject: Fix [3cd9bea1e6]: check-in [43032d7ba3] potential problems. Also don't allow surrogates in \U?????? syntax. --- generic/tclParse.c | 21 ++++++++----- tests/string.test | 89 +++++++++++++++++++++++++----------------------------- tests/utf.test | 72 ++++++++++++++++++++++++------------------- 3 files changed, 95 insertions(+), 87 deletions(-) diff --git a/generic/tclParse.c b/generic/tclParse.c index 4f30f8b..b42ff5c 100644 --- a/generic/tclParse.c +++ b/generic/tclParse.c @@ -870,7 +870,7 @@ TclParseBackslash( count = 2; switch (*p) { /* - * Note: in the conversions below, use absolute values (e.g., 0xa) + * Note: in the conversions below, use absolute values (e.g., 0xA) * rather than symbolic values (e.g. \n) that get converted by the * compiler. It's possible that compilers on some platforms will do * the symbolic conversions differently, which could result in @@ -884,19 +884,19 @@ TclParseBackslash( result = 0x8; break; case 'f': - result = 0xc; + result = 0xC; break; case 'n': - result = 0xa; + result = 0xA; break; case 'r': - result = 0xd; + result = 0xD; break; case 't': result = 0x9; break; case 'v': - result = 0xb; + result = 0xB; break; case 'x': count += TclParseHex(p+1, (numBytes > 3) ? 2 : numBytes-2, &result); @@ -921,11 +921,13 @@ TclParseBackslash( */ result = 'u'; #if TCL_UTF_MAX > 3 - } else if (((result & 0xDC00) == 0xD800) && (count == 6) && (p[5] == '\\') && (p[6] == 'u') && (numBytes >= 10)) { - /* If high surrogate is immediately followed by a low surrogate escape, combine them. */ + } else if (((result & 0xDC00) == 0xD800) && (count == 6) + && (p[5] == '\\') && (p[6] == 'u') && (numBytes >= 10)) { + /* If high surrogate is immediately followed by a low surrogate + * escape, combine them into one character. */ int low; int count2 = TclParseHex(p+7, 4, &low); - if ((low & 0xDC00) == 0xDC00) { + if ((count2 == 4) && ((low & 0xDC00) == 0xDC00)) { result = ((result & 0x3FF)<<10 | (low & 0x3FF)) + 0x10000; count += count2 + 2; } @@ -939,6 +941,9 @@ TclParseBackslash( * No hexadigits -> This is just "U". */ result = 'U'; + } else if ((result | 0x7FF) == 0xDFFF) { + /* Upper or lower surrogate, not allowed in this syntax. */ + result = 0xFFFD; } break; case '\n': diff --git a/tests/string.test b/tests/string.test index a3590e5..f5defd9 100644 --- a/tests/string.test +++ b/tests/string.test @@ -222,7 +222,7 @@ test string-4.15 {string first, ability to two-byte encoded utf-8 chars} { # Test for a bug in Tcl 8.3 where test for all-single-byte-encoded # strings was incorrect, leading to an index returned by [string first] # which pointed past the end of the string. - set uchar \u057e ;# character with two-byte encoding in utf-8 + set uchar \u057E ;# character with two-byte encoding in utf-8 string first % %#$uchar$uchar#$uchar$uchar#% 3 } 8 test string-4.17 {string first, corner case} { @@ -309,9 +309,6 @@ test string-5.19 {string index, bytearray object out of bounds} { test string-5.20 {string index, bytearray object out of bounds} { string index [binary format I* {0x50515253 0x52}] 20 } {} -test string-5.21 {string index, surrogates, bug [11ae2be95dac9417]} tip389 { - list [string index a\U100000b 1] [string index a\U100000b 2] [string index a\U100000b 3] -} [list \U100000 {} b] proc largest_int {} { @@ -395,7 +392,7 @@ test string-6.24 {string is digit, true} { string is digit 0123456789 } 1 test string-6.25 {string is digit, false} { - list [string is digit -fail var 0123\u00dc567] $var + list [string is digit -fail var 0123\u00DC567] $var } {0 4} test string-6.26 {string is digit, false} { list [string is digit -fail var +123567] $var @@ -518,7 +515,7 @@ test string-6.60 {string is lower, true} { string is lower abc } 1 test string-6.61 {string is lower, unicode true} { - string is lower abc\u00fcue + string is lower abc\u00FCue } 1 test string-6.62 {string is lower, false} { list [string is lower -fail var aBc] $var @@ -527,7 +524,7 @@ test string-6.63 {string is lower, false} { list [string is lower -fail var abc1] $var } {0 3} test string-6.64 {string is lower, unicode false} { - list [string is lower -fail var ab\u00dcUE] $var + list [string is lower -fail var ab\u00DCUE] $var } {0 2} test string-6.65 {string is space, true} { string is space " \t\n\v\f" @@ -565,7 +562,7 @@ test string-6.75 {string is upper, true} { string is upper ABC } 1 test string-6.76 {string is upper, unicode true} { - string is upper ABC\u00dcUE + string is upper ABC\u00DCUE } 1 test string-6.77 {string is upper, false} { list [string is upper -fail var AbC] $var @@ -574,13 +571,13 @@ test string-6.78 {string is upper, false} { list [string is upper -fail var AB2C] $var } {0 2} test string-6.79 {string is upper, unicode false} { - list [string is upper -fail var ABC\u00fcue] $var + list [string is upper -fail var ABC\u00FCue] $var } {0 3} test string-6.80 {string is wordchar, true} { string is wordchar abc_123 } 1 test string-6.81 {string is wordchar, unicode true} { - string is wordchar abc\u00fcab\u00dcAB\u5001 + string is wordchar abc\u00FCab\u00DCAB\u5001 } 1 test string-6.82 {string is wordchar, false} { list [string is wordchar -fail var abcd.ef] $var @@ -606,7 +603,7 @@ test string-6.87 {string is print} { } {0 15} test string-6.88 {string is punct} { ## any graph char that isn't alnum - list [string is punct -fail var "_!@#\u00beq0"] $var + list [string is punct -fail var "_!@#\u00BEq0"] $var } {0 4} test string-6.89 {string is xdigit} { list [string is xdigit -fail var 0123456789\u0061bcdefABCDEFg] $var @@ -699,7 +696,7 @@ test string-6.108 {string is double, Bug 1382287} { string is double $x } 0 test string-6.109 {string is double, Bug 1360532} { - string is double 1\u00a0 + string is double 1\u00A0 } 0 test string-6.110 {string is entier, true} { string is entier +1234567890 @@ -1278,8 +1275,8 @@ test string-12.19 {string range, bytearray object} { string equal $r1 $r2 } 1 test string-12.20 {string range, out of bounds indices} { - string range \u00ff 0 1 -} \u00ff + string range \u00FF 0 1 +} \u00FF # Bug 1410553 test string-12.21 {string range, regenerates correct reps, bug 1410553} { set bytes "\x00 \x03 \x41" @@ -1490,18 +1487,14 @@ test string-17.5 {string totitle} { string totitle {123#$&*()} } {123#$&*()} test string-17.6 {string totitle, unicode} { - string totitle ABCabc\xc7\xe7 -} "Abcabc\xe7\xe7" + string totitle ABCabc\xC7\xE7 +} "Abcabc\xE7\xE7" test string-17.7 {string totitle, unicode} { - string totitle \u01f3BCabc\xc7\xe7 -} "\u01f2bcabc\xe7\xe7" + string totitle \u01F3BCabc\xc7\xe7 +} "\u01F2bcabc\xe7\xe7" test string-17.8 {string totitle, compiled} { lindex [string totitle [list aa bb [list cc]]] 0 } Aa -test string-17.9 {string totitle, surrogates, bug [11ae2be95dac9417]} tip389 { - list [string totitle a\U118c0c 1 1] [string totitle a\U118c0c 2 2] \ - [string totitle a\U118c0c 3 3] -} [list a\U118a0c a\U118c0C a\U118c0C] test string-18.1 {string trim} { list [catch {string trim} msg] $msg @@ -1537,7 +1530,7 @@ test string-18.11 {string trim, unicode} { string trim "\xe7\xe8 AB\xe7C \xe8\xe7" \xe7\xe8 } " AB\xe7C " test string-18.12 {string trim, unicode default} { - string trim \ufeff\x00\u0085\u00a0\u1680\u180eABC\u1361\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000 + string trim \uFEFF\x00\u0085\u00A0\u1680\u180EABC\u1361\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u2028\u2029\u202F\u205F\u3000 } ABC\u1361 test string-19.1 {string trimleft} { @@ -1547,7 +1540,7 @@ test string-19.2 {string trimleft} { string trimleft " XYZ " } {XYZ } test string-19.3 {string trimleft, unicode default} { - string trimleft \ufeff\u0085\u00a0\x00\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000\u1361ABC + string trimleft \uFEFF\u0085\u00A0\x00\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u2028\u2029\u202F\u205F\u3000\u1361ABC } \u1361ABC test string-20.1 {string trimright errors} { @@ -1566,7 +1559,7 @@ test string-20.5 {string trimright} { string trimright "" } {} test string-20.6 {string trimright, unicode default} { - string trimright ABC\u1361\u0085\x00\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000 + string trimright ABC\u1361\u0085\x00\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u2028\u2029\u202F\u205F\u3000 } ABC\u1361 test string-21.1 {string wordend} { @@ -1597,19 +1590,19 @@ test string-21.9 {string wordend} { string worde "x.y" end-1 } 2 test string-21.10 {string wordend, unicode} { - string wordend "xyz\u00c7de fg" 0 + string wordend "xyz\u00C7de fg" 0 } 6 test string-21.11 {string wordend, unicode} { - string wordend "xyz\uc700de fg" 0 + string wordend "xyz\uC700de fg" 0 } 6 test string-21.12 {string wordend, unicode} { - string wordend "xyz\u203fde fg" 0 + string wordend "xyz\u203Fde fg" 0 } 6 test string-21.13 {string wordend, unicode} { string wordend "xyz\u2045de fg" 0 } 3 test string-21.14 {string wordend, unicode} { - string wordend "\uc700\uc700 abc" 8 + string wordend "\uC700\uC700 abc" 8 } 6 test string-22.1 {string wordstart} { @@ -1643,13 +1636,13 @@ test string-22.10 {string wordstart} { string wordstart "one two three" end-5 } 7 test string-22.11 {string wordstart, unicode} { - string wordstart "one tw\u00c7o three" 7 + string wordstart "one tw\u00C7o three" 7 } 4 test string-22.12 {string wordstart, unicode} { - string wordstart "ab\uc700\uc700 cdef ghi" 12 + string wordstart "ab\uC700\uC700 cdef ghi" 12 } 10 test string-22.13 {string wordstart, unicode} { - string wordstart "\uc700\uc700 abc" 8 + string wordstart "\uC700\uC700 abc" 8 } 3 test string-23.0 {string is boolean, Bug 1187123} testindexobj { @@ -1720,40 +1713,40 @@ test string-24.4 {string reverse command - unshared string} { string reverse $x$y } edcba test string-24.5 {string reverse command - shared unicode string} { - set x abcde\ud0ad + set x abcde\uD0AD string reverse $x -} \ud0adedcba +} \uD0ADedcba test string-24.6 {string reverse command - unshared string} { set x abc - set y de\ud0ad + set y de\uD0AD string reverse $x$y -} \ud0adedcba +} \uD0ADedcba test string-24.7 {string reverse command - simple case} { string reverse a } a test string-24.8 {string reverse command - simple case} { - string reverse \ud0ad -} \ud0ad + string reverse \uD0AD +} \uD0AD test string-24.9 {string reverse command - simple case} { string reverse {} } {} test string-24.10 {string reverse command - corner case} { - set x \ubeef\ud0ad + set x \uBEEF\uD0AD string reverse $x -} \ud0ad\ubeef +} \uD0AD\uBEEF test string-24.11 {string reverse command - corner case} { - set x \ubeef - set y \ud0ad + set x \uBEEF + set y \uD0AD string reverse $x$y -} \ud0ad\ubeef +} \uD0AD\uBEEF test string-24.12 {string reverse command - corner case} { - set x \ubeef - set y \ud0ad + set x \uBEEF + set y \uD0AD string is ascii [string reverse $x$y] } 0 test string-24.13 {string reverse command - pure Unicode string} { - string reverse [string range \ubeef\ud0ad\ubeef\ud0ad\ubeef\ud0ad 1 5] -} \ud0ad\ubeef\ud0ad\ubeef\ud0ad + string reverse [string range \uBEEF\uD0AD\uBEEF\uD0AD\uBEEF\uD0AD 1 5] +} \uD0AD\uBEEF\uD0AD\uBEEF\uD0AD test string-24.14 {string reverse command - pure bytearray} { binary scan [string reverse [binary format H* 010203]] H* x set x @@ -1809,7 +1802,7 @@ test string-25.13 {string is list} { } {0 2} test string-25.14 {string is list} { set x {} - list [string is list -failindex x "\uabcd {b c}d e"] $x + list [string is list -failindex x "\uABCD {b c}d e"] $x } {0 2} test string-26.1 {tcl::prefix, too few args} -body { diff --git a/tests/utf.test b/tests/utf.test index 1e257d3..2e4882d 100644 --- a/tests/utf.test +++ b/tests/utf.test @@ -22,6 +22,7 @@ catch {unset x} # Some tests require support for 4-byte UTF-8 sequences testConstraint fullutf [expr {[format %c 0x010000] != "\uFFFD"}] +testConstraint tip389 [expr {[string length \U010000] == 2}] test utf-1.1 {Tcl_UniCharToUtf: 1 byte sequences} testbytestring { expr {"\x01" eq [testbytestring "\x01"]} @@ -59,6 +60,9 @@ test utf-1.11 {Tcl_UniCharToUtf: 3 byte sequence, low surrogate} testbytestring test utf-1.12 {Tcl_UniCharToUtf: 4 byte sequence, high/low surrogate} {fullutf testbytestring} { expr {"\uD842\uDC42" eq [testbytestring "\xF0\xA0\xA1\x82"]} } 1 +test utf-1.13 {Tcl_UniCharToUtf: Invalid surrogate} testbytestring { + expr {"\UD842" eq [testbytestring "\xEF\xBF\xBD"]} +} 1 test utf-2.1 {Tcl_UtfToUniChar: low ascii} { string length "abc" @@ -81,10 +85,10 @@ test utf-2.6 {Tcl_UtfToUniChar: lead (3-byte) followed by 1 trail} testbytestrin test utf-2.7 {Tcl_UtfToUniChar: lead (3-byte) followed by 2 trail} testbytestring { string length [testbytestring "\xE4\xB9\x8E"] } {1} -test utf-2.8 {Tcl_UtfToUniChar: lead (4-byte) followed by 3 trail} -constraints {fullutf testbytestring} -body { +test utf-2.8 {Tcl_UtfToUniChar: lead (4-byte) followed by 3 trail} -constraints {tip389 testbytestring} -body { string length [testbytestring "\xF0\x90\x80\x80"] } -result {2} -test utf-2.9 {Tcl_UtfToUniChar: lead (4-byte) followed by 3 trail} -constraints {fullutf testbytestring} -body { +test utf-2.9 {Tcl_UtfToUniChar: lead (4-byte) followed by 3 trail} -constraints {tip389 testbytestring} -body { string length [testbytestring "\xF4\x8F\xBF\xBF"] } -result {2} test utf-2.10 {Tcl_UtfToUniChar: lead (4-byte) followed by 3 trail, underflow} testbytestring { @@ -160,7 +164,7 @@ test utf-8.3 {Tcl_UniCharAtIndex: index > 0} { } {c} test utf-8.4 {Tcl_UniCharAtIndex: index > 0} { string index \u4E4E\u25A\xFF\u543 2 -} "\uff" +} "\uFF" test utf-9.1 {Tcl_UtfAtIndex: index = 0} { string range abcd 0 2 @@ -186,6 +190,12 @@ test utf-10.4 {Tcl_UtfBackslash: stops at first non-hex} testbytestring { test utf-10.5 {Tcl_UtfBackslash: stops after 4 hex chars} testbytestring { expr {"\u4E216" eq "[testbytestring \xE4\xB8\xA1]6"} } 1 +test utf-10.6 {Tcl_UtfBackslash: stops after 5 hex chars} {fullutf testbytestring} { + expr {"\U1E2165" eq "[testbytestring \xF0\x9E\x88\x96]5"} +} 1 +test utf-10.7 {Tcl_UtfBackslash: stops after 6 hex chars} {fullutf testbytestring} { + expr {"\U10E2165" eq "[testbytestring \xF4\x8E\x88\x96]5"} +} 1 proc bsCheck {char num} { global errNum test utf-10.$errNum {backslash substitution} { @@ -194,7 +204,7 @@ proc bsCheck {char num} { } $num incr errNum } -set errNum 6 +set errNum 8 bsCheck \b 8 bsCheck \e 101 bsCheck \f 12 @@ -260,10 +270,10 @@ test utf-11.2 {Tcl_UtfToUpper} { string toupper abc } ABC test utf-11.3 {Tcl_UtfToUpper} { - string toupper \u00E3ab + string toupper \u00E3AB } \u00C3AB test utf-11.4 {Tcl_UtfToUpper} { - string toupper \u01E3ab + string toupper \u01E3AB } \u01E2AB test utf-11.5 {Tcl_UtfToUpper Georgian (new in Unicode 11)} { string toupper \u10D0\u1C90 @@ -292,10 +302,10 @@ test utf-13.2 {Tcl_UtfToTitle} { string totitle abc } Abc test utf-13.3 {Tcl_UtfToTitle} { - string totitle \u00E3ab + string totitle \u00E3AB } \u00C3ab test utf-13.4 {Tcl_UtfToTitle} { - string totitle \u01F3ab + string totitle \u01F3AB } \u01F2ab test utf-13.5 {Tcl_UtfToTitle Georgian (new in Unicode 11)} { string totitle \u10D0\u1C90 @@ -321,7 +331,7 @@ test utf-15.1 {Tcl_UniCharToUpper, negative delta} { string toupper aA } AA test utf-15.2 {Tcl_UniCharToUpper, positive delta} { - string toupper \u0178\u00ff + string toupper \u0178\u00FF } \u0178\u0178 test utf-15.3 {Tcl_UniCharToUpper, no delta} { string toupper ! @@ -331,24 +341,24 @@ test utf-16.1 {Tcl_UniCharToLower, negative delta} { string tolower aA } aa test utf-16.2 {Tcl_UniCharToLower, positive delta} { - string tolower \u0178\u00ff\uA78D\u01c5 -} \u00ff\u00ff\u0265\u01c6 + string tolower \u0178\u00FF\uA78D\u01C5 +} \u00FF\u00FF\u0265\u01C6 test utf-17.1 {Tcl_UniCharToLower, no delta} { string tolower ! } ! test utf-18.1 {Tcl_UniCharToTitle, add one for title} { - string totitle \u01c4 -} \u01c5 + string totitle \u01C4 +} \u01C5 test utf-18.2 {Tcl_UniCharToTitle, subtract one for title} { - string totitle \u01c6 -} \u01c5 + string totitle \u01C6 +} \u01C5 test utf-18.3 {Tcl_UniCharToTitle, subtract delta for title (positive)} { - string totitle \u017f + string totitle \u017F } \u0053 test utf-18.4 {Tcl_UniCharToTitle, subtract delta for title (negative)} { - string totitle \u00ff + string totitle \u00FF } \u0178 test utf-18.5 {Tcl_UniCharToTitle, no delta} { string totitle ! @@ -365,15 +375,15 @@ test utf-20.1 {TclUniCharNcmp} { test utf-21.1 {TclUniCharIsAlnum} { # this returns 1 with Unicode 7 compliance - string is alnum \u1040\u021f\u0220 + string is alnum \u1040\u021F\u0220 } {1} test utf-21.2 {unicode alnum char in regc_locale.c} { # this returns 1 with Unicode 7 compliance - list [regexp {^[[:alnum:]]+$} \u1040\u021f\u0220] [regexp {^\w+$} \u1040\u021f\u0220_\u203f\u2040\u2054\ufe33\ufe34\ufe4d\ufe4e\ufe4f\uff3f] + list [regexp {^[[:alnum:]]+$} \u1040\u021F\u0220] [regexp {^\w+$} \u1040\u021F\u0220_\u203F\u2040\u2054\uFE33\uFE34\uFE4D\uFE4E\uFE4F\uFF3F] } {1 1} test utf-21.3 {unicode print char in regc_locale.c} { # this returns 1 with Unicode 7 compliance - regexp {^[[:print:]]+$} \ufbc1 + regexp {^[[:print:]]+$} \uFBC1 } 1 test utf-21.4 {TclUniCharIsGraph} { # [Bug 3464428] @@ -385,11 +395,11 @@ test utf-21.5 {unicode graph char in regc_locale.c} { } {1} test utf-21.6 {TclUniCharIsGraph} { # [Bug 3464428] - string is graph \u00a0 + string is graph \u00A0 } {0} test utf-21.7 {unicode graph char in regc_locale.c} { # [Bug 3464428] - regexp {[[:graph:]]} \u0020\u00a0\u2028\u2029 + regexp {[[:graph:]]} \u0020\u00A0\u2028\u2029 } {0} test utf-21.8 {TclUniCharIsPrint} { # [Bug 3464428] @@ -405,45 +415,45 @@ test utf-21.10 {unicode print char in regc_locale.c} { } {0} test utf-21.11 {TclUniCharIsControl} { # [Bug 3464428] - string is control \u0000\u001f\u00ad\u0605\u061c\u180e\u2066\ufeff + string is control \u0000\u001F\u00AD\u0605\u061C\u180E\u2066\uFEFF } {1} test utf-21.12 {unicode control char in regc_locale.c} { # [Bug 3464428], [Bug a876646efe] - regexp {^[[:cntrl:]]*$} \u0000\u001f\u00ad\u0605\u061c\u180e\u2066\ufeff + regexp {^[[:cntrl:]]*$} \u0000\u001F\u00AD\u0605\u061C\u180E\u2066\uFEFF } {1} test utf-22.1 {TclUniCharIsWordChar} { string wordend "xyz123_bar fg" 0 } 10 test utf-22.2 {TclUniCharIsWordChar} { - string wordend "x\u5080z123_bar\u203c fg" 0 + string wordend "x\u5080z123_bar\u203C fg" 0 } 10 test utf-23.1 {TclUniCharIsAlpha} { # this returns 1 with Unicode 7 compliance - string is alpha \u021f\u0220\u037f\u052f + string is alpha \u021F\u0220\u037F\u052F } {1} test utf-23.2 {unicode alpha char in regc_locale.c} { # this returns 1 with Unicode 7 compliance - regexp {^[[:alpha:]]+$} \u021f\u0220\u037f\u052f + regexp {^[[:alpha:]]+$} \u021F\u0220\u037F\u052F } {1} test utf-24.1 {TclUniCharIsDigit} { # this returns 1 with Unicode 7 compliance - string is digit \u1040\uabf0 + string is digit \u1040\uABF0 } {1} test utf-24.2 {unicode digit char in regc_locale.c} { # this returns 1 with Unicode 7 compliance - list [regexp {^[[:digit:]]+$} \u1040\uabf0] [regexp {^\d+$} \u1040\uabf0] + list [regexp {^[[:digit:]]+$} \u1040\uABF0] [regexp {^\d+$} \u1040\uABF0] } {1 1} test utf-24.3 {TclUniCharIsSpace} { # this returns 1 with Unicode 7/TIP 413 compliance - string is space \u0085\u1680\u180e\u200b\u202f\u2060 + string is space \u0085\u1680\u180E\u200B\u202F\u2060 } {1} test utf-24.4 {unicode space char in regc_locale.c} { # this returns 1 with Unicode 7/TIP 413 compliance - list [regexp {^[[:space:]]+$} \u0085\u1680\u180e\u200b\u202f\u2060] [regexp {^\s+$} \u0085\u1680\u180e\u200b\u202f\u2060] + list [regexp {^[[:space:]]+$} \u0085\u1680\u180E\u200B\u202F\u2060] [regexp {^\s+$} \u0085\u1680\u180E\u200B\u202F\u2060] } {1 1} testConstraint teststringobj [llength [info commands teststringobj]] -- cgit v0.12