From 3c9c7e062138b5f21935974d667eec0ae10c346c Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Fri, 16 Apr 2021 20:34:33 +0000 Subject: Remove wtf-8/wtf-16/tcl-8 encodings --- doc/string.n | 4 +- generic/tclEncoding.c | 40 ++++++-------------- library/init.tcl | 4 +- tests/encoding.test | 100 +++++++++----------------------------------------- 4 files changed, 32 insertions(+), 116 deletions(-) diff --git a/doc/string.n b/doc/string.n index f1a0592..f3d7616 100644 --- a/doc/string.n +++ b/doc/string.n @@ -415,11 +415,11 @@ etc.) .PP \fICompatibility note:\fR This subcommand is deprecated and will be removed in Tcl 9.0. It is better to use the \fBencoding convertto\fR -command to convert a string to a known encoding (e.g. "wtf-8" or "tcl-8") +command to convert a string to a known encoding (e.g. "utf-8" or "cesu-8") and then apply \fBstring length\fR to that. .PP .CS -\fBstring length\fR [encoding convertto wtf-8 $theString] +\fBstring length\fR [encoding convertto utf-8 $theString] .CE .RE .TP diff --git a/generic/tclEncoding.c b/generic/tclEncoding.c index 29aeefd..21c254e 100644 --- a/generic/tclEncoding.c +++ b/generic/tclEncoding.c @@ -511,11 +511,10 @@ FillEncodingFileMap(void) */ /* Those flags must not conflict with other TCL_ENCODING_* flags in tcl.h */ -/* Since TCL_ENCODING_MODIFIED is only used for utf-8/wtf-8/cesu-8 and - * TCL_ENCODING_LE is only used for utf-16/wtf-16/ucs-2. re-use the same value */ +/* Since TCL_ENCODING_MODIFIED is only used for utf-8/cesu-8 and + * TCL_ENCODING_LE is only used for utf-16/ucs-2. re-use the same value */ #define TCL_ENCODING_MODIFIED 0x20 /* Converting NULL bytes to 0xC0 0x80 */ #define TCL_ENCODING_LE TCL_ENCODING_MODIFIED /* Little-endian encoding */ -#define TCL_ENCODING_WTF 0x100 /* For WTF-8 encoding, don't check for surrogates/noncharacters */ #define TCL_ENCODING_UTF 0x200 /* For UTF-8 encoding, allow 4-byte output sequences */ void @@ -560,15 +559,9 @@ TclInitEncodingSubsystem(void) type.nullSize = 1; type.clientData = INT2PTR(TCL_ENCODING_UTF); Tcl_CreateEncoding(&type); - type.clientData = INT2PTR(TCL_ENCODING_UTF|TCL_ENCODING_WTF); - type.encodingName = "wtf-8"; - Tcl_CreateEncoding(&type); type.clientData = INT2PTR(0); type.encodingName = "cesu-8"; Tcl_CreateEncoding(&type); - type.clientData = INT2PTR(TCL_ENCODING_UTF|TCL_ENCODING_WTF|TCL_ENCODING_MODIFIED); - type.encodingName = "tcl-8"; - Tcl_CreateEncoding(&type); type.toUtfProc = Utf16ToUtfProc; type.fromUtfProc = UtfToUcs2Proc; @@ -591,21 +584,12 @@ TclInitEncodingSubsystem(void) type.encodingName = "utf-16le"; type.clientData = INT2PTR(TCL_ENCODING_LE); Tcl_CreateEncoding(&type); - type.encodingName = "wtf-16le"; - type.clientData = INT2PTR(TCL_ENCODING_LE + TCL_ENCODING_WTF); - Tcl_CreateEncoding(&type); type.encodingName = "utf-16be"; type.clientData = INT2PTR(0); Tcl_CreateEncoding(&type); - type.encodingName = "wtf-16be"; - type.clientData = INT2PTR(TCL_ENCODING_WTF); - Tcl_CreateEncoding(&type); type.encodingName = "utf-16"; type.clientData = INT2PTR(isLe.c); Tcl_CreateEncoding(&type); - type.encodingName = "wtf-16"; - type.clientData = INT2PTR(isLe.c + TCL_ENCODING_WTF); - Tcl_CreateEncoding(&type); #ifndef TCL_NO_DEPRECATED type.encodingName = "unicode"; @@ -2315,15 +2299,13 @@ UtfToUtfProc( len = (src <= srcEnd-3) ? TclUtfToUCS4(src, &low) : 0; if (((low & ~0x3FF) != 0xDC00) || (ch & 0x400)) { - if (!(flags & TCL_ENCODING_WTF)) { - if (flags & TCL_ENCODING_STOPONERROR) { - result = TCL_CONVERT_UNKNOWN; - src = saveSrc; - break; - } - if (!(flags & TCL_ENCODING_MODIFIED)) { - ch = 0xFFFD; - } + if (flags & TCL_ENCODING_STOPONERROR) { + result = TCL_CONVERT_UNKNOWN; + src = saveSrc; + break; + } + if (!(flags & TCL_ENCODING_MODIFIED)) { + ch = 0xFFFD; } cesu8: *dst++ = (char) (((ch >> 12) | 0xE0) & 0xEF); @@ -2334,7 +2316,7 @@ UtfToUtfProc( src += len; dst += Tcl_UniCharToUtf(ch, dst); ch = low; - } else if (!(flags & TCL_ENCODING_WTF) && !Tcl_UniCharIsUnicode(ch)) { + } else if (!Tcl_UniCharIsUnicode(ch)) { if (flags & TCL_ENCODING_STOPONERROR) { result = TCL_CONVERT_UNKNOWN; src = saveSrc; @@ -2530,7 +2512,7 @@ UtfToUtf16Proc( break; } len = TclUtfToUCS4(src, &ch); - if (!(flags & TCL_ENCODING_WTF) && !Tcl_UniCharIsUnicode(ch)) { + if (!Tcl_UniCharIsUnicode(ch)) { if (flags & TCL_ENCODING_STOPONERROR) { result = TCL_CONVERT_UNKNOWN; break; diff --git a/library/init.tcl b/library/init.tcl index 749eed9..e30296e 100644 --- a/library/init.tcl +++ b/library/init.tcl @@ -214,9 +214,9 @@ proc unknown args { set errInfo [dict get $opts -errorinfo] set errCode [dict get $opts -errorcode] set cinfo $args - if {[string length [encoding convertto wtf-8 $cinfo]] > 150} { + if {[string length [encoding convertto utf-8 $cinfo]] > 150} { set cinfo [string range $cinfo 0 150] - while {[string length [encoding convertto wtf-8 $cinfo]] > 150} { + while {[string length [encoding convertto utf-8 $cinfo]] > 150} { set cinfo [string range $cinfo 0 end-1] } append cinfo ... diff --git a/tests/encoding.test b/tests/encoding.test index 9924886..82a2d6b 100644 --- a/tests/encoding.test +++ b/tests/encoding.test @@ -338,138 +338,78 @@ test encoding-15.5 {UtfToUtfProc emoji character input} { set y [encoding convertfrom utf-8 \xF0\x9F\x98\x82] list [string length $x] $y } "4 😂" -test encoding-15.6 {UtfToUtfProc emoji character output} { - set x \uDE02\uD83D\uDE02\uD83D - set y [encoding convertto wtf-8 \uDE02\uD83D\uDE02\uD83D] - binary scan $y H* z - list [string length $y] $z -} {10 edb882f09f9882eda0bd} -test encoding-15.7 {UtfToUtfProc emoji character output} { - set x \uDE02\uD83D\uD83D - set y [encoding convertto wtf-8 \uDE02\uD83D\uD83D] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {3 9 edb882eda0bdeda0bd} -test encoding-15.8 {UtfToUtfProc emoji character output} { - set x \uDE02\uD83Dé - set y [encoding convertto wtf-8 \uDE02\uD83Dé] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {3 8 edb882eda0bdc3a9} -test encoding-15.9 {UtfToUtfProc emoji character output} { - set x \uDE02\uD83DX - set y [encoding convertto wtf-8 \uDE02\uD83DX] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {3 7 edb882eda0bd58} -test encoding-15.10 {UtfToUtfProc high surrogate character output} { - set x \uDE02é - set y [encoding convertto wtf-8 \uDE02é] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {2 5 edb882c3a9} -test encoding-15.11 {UtfToUtfProc low surrogate character output} { - set x \uDA02é - set y [encoding convertto wtf-8 \uDA02é] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {2 5 eda882c3a9} -test encoding-15.12 {UtfToUtfProc high surrogate character output} { - set x \uDE02Y - set y [encoding convertto wtf-8 \uDE02Y] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {2 4 edb88259} -test encoding-15.13 {UtfToUtfProc low surrogate character output} { - set x \uDA02Y - set y [encoding convertto wtf-8 \uDA02Y] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {2 4 eda88259} -test encoding-15.14 {UtfToUtfProc high surrogate character output} { - set x \uDE02 - set y [encoding convertto wtf-8 \uDE02] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {1 3 edb882} -test encoding-15.15 {UtfToUtfProc low surrogate character output} { - set x \uDA02 - set y [encoding convertto wtf-8 \uDA02] - binary scan $y H* z - list [string length $x] [string length $y] $z -} {1 3 eda882} -test encoding-15.16 {UtfToUtfProc: Invalid 4-byte UTF-8, see [ed29806ba]} { +test encoding-15.6 {UtfToUtfProc: Invalid 4-byte UTF-8, see [ed29806ba]} { set x \xF0\xA0\xA1\xC2 set y [encoding convertfrom utf-8 \xF0\xA0\xA1\xC2] list [string length $x] $y } "4 \xF0\xA0\xA1\xC2" -test encoding-15.17 {UtfToUtfProc emoji character output} { +test encoding-15.7 {UtfToUtfProc emoji character output} { set x 😂 set y [encoding convertto utf-8 😂] binary scan $y H* z list [string length $y] $z } {4 f09f9882} -test encoding-15.18 {UtfToUtfProc emoji character output} { +test encoding-15.8 {UtfToUtfProc emoji character output} { set x \uDE02\uD83D\uDE02\uD83D set y [encoding convertto utf-8 \uDE02\uD83D\uDE02\uD83D] binary scan $y H* z list [string length $y] $z } {10 efbfbdf09f9882efbfbd} -test encoding-15.19 {UtfToUtfProc emoji character output} { +test encoding-15.9 {UtfToUtfProc emoji character output} { set x \uDE02\uD83D\uD83D set y [encoding convertto utf-8 \uDE02\uD83D\uD83D] binary scan $y H* z list [string length $x] [string length $y] $z } {3 9 efbfbdefbfbdefbfbd} -test encoding-15.20 {UtfToUtfProc emoji character output} { +test encoding-15.10 {UtfToUtfProc emoji character output} { set x \uDE02\uD83D\xE9 set y [encoding convertto utf-8 \uDE02\uD83D\xE9] binary scan $y H* z list [string length $x] [string length $y] $z } {3 8 efbfbdefbfbdc3a9} -test encoding-15.21 {UtfToUtfProc emoji character output} { +test encoding-15.11 {UtfToUtfProc emoji character output} { set x \uDE02\uD83DX set y [encoding convertto utf-8 \uDE02\uD83DX] binary scan $y H* z list [string length $x] [string length $y] $z } {3 7 efbfbdefbfbd58} -test encoding-15.22 {UtfToUtfProc high surrogate character output} { +test encoding-15.12 {UtfToUtfProc high surrogate character output} { set x \uDE02\xE9 set y [encoding convertto utf-8 \uDE02\xE9] binary scan $y H* z list [string length $x] [string length $y] $z } {2 5 efbfbdc3a9} -test encoding-15.23 {UtfToUtfProc low surrogate character output} { +test encoding-15.13 {UtfToUtfProc low surrogate character output} { set x \uDA02\xE9 set y [encoding convertto utf-8 \uDA02\xE9] binary scan $y H* z list [string length $x] [string length $y] $z } {2 5 efbfbdc3a9} -test encoding-15.24 {UtfToUtfProc high surrogate character output} { +test encoding-15.14 {UtfToUtfProc high surrogate character output} { set x \uDE02Y set y [encoding convertto utf-8 \uDE02Y] binary scan $y H* z list [string length $x] [string length $y] $z } {2 4 efbfbd59} -test encoding-15.25 {UtfToUtfProc low surrogate character output} { +test encoding-15.15 {UtfToUtfProc low surrogate character output} { set x \uDA02Y set y [encoding convertto utf-8 \uDA02Y] binary scan $y H* z list [string length $x] [string length $y] $z } {2 4 efbfbd59} -test encoding-15.26 {UtfToUtfProc high surrogate character output} { +test encoding-15.16 {UtfToUtfProc high surrogate character output} { set x \uDE02 set y [encoding convertto utf-8 \uDE02] binary scan $y H* z list [string length $x] [string length $y] $z } {1 3 efbfbd} -test encoding-15.27 {UtfToUtfProc low surrogate character output} { +test encoding-15.17 {UtfToUtfProc low surrogate character output} { set x \uDA02 set y [encoding convertto utf-8 \uDA02] binary scan $y H* z list [string length $x] [string length $y] $z } {1 3 efbfbd} -test encoding-15.28 {UtfToUtfProc CESU-8 6-byte sequence} { +test encoding-15.18 {UtfToUtfProc CESU-8 6-byte sequence} { set y [encoding convertto cesu-8 \U10000] binary scan $y H* z list [string length $y] $z @@ -499,19 +439,13 @@ test encoding-16.4 {Ucs2ToUtfProc} -body { test encoding-17.1 {UtfToUtf16Proc} -body { encoding convertto utf-16 "\U460DC" } -result "\xD8\xD8\xDC\xDC" -test encoding-17.2 {UtfToUtf16Proc} -body { - encoding convertto wtf-16 "\uDCDC" -} -result "\xDC\xDC" -test encoding-17.3 {UtfToUtf16Proc} -body { - encoding convertto wtf-16 "\uD8D8" -} -result "\xD8\xD8" -test encoding-17.4 {UtfToUcs2Proc} -body { +test encoding-17.2 {UtfToUcs2Proc} -body { encoding convertfrom utf-16 [encoding convertto ucs-2 "\U460DC"] } -result "\uFFFD" -test encoding-17.5 {UtfToUtf16Proc} -body { +test encoding-17.3 {UtfToUtf16Proc} -body { encoding convertto utf-16be "\uDCDC" } -result "\xFF\xFD" -test encoding-17.6 {UtfToUtf16Proc} -body { +test encoding-17.4 {UtfToUtf16Proc} -body { encoding convertto utf-16le "\uD8D8" } -result "\xFD\xFF" @@ -813,7 +747,7 @@ test encoding-28.0 {all encodings load} -body { llength $name } return $count -} -result [expr {[info exists ::tcl_precision] ? 92 : 91}] +} -result [expr {[info exists ::tcl_precision] ? 87 : 86}] runtests -- cgit v0.12 From 82d0339a6592bd7855cc08df656c252dda1352c5 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Fri, 16 Apr 2021 20:45:56 +0000 Subject: renumber testcases --- tests/encoding.test | 54 ++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/encoding.test b/tests/encoding.test index 82a2d6b..e1c55d7 100644 --- a/tests/encoding.test +++ b/tests/encoding.test @@ -338,77 +338,77 @@ test encoding-15.5 {UtfToUtfProc emoji character input} { set y [encoding convertfrom utf-8 \xF0\x9F\x98\x82] list [string length $x] $y } "4 😂" -test encoding-15.6 {UtfToUtfProc: Invalid 4-byte UTF-8, see [ed29806ba]} { - set x \xF0\xA0\xA1\xC2 - set y [encoding convertfrom utf-8 \xF0\xA0\xA1\xC2] - list [string length $x] $y -} "4 \xF0\xA0\xA1\xC2" -test encoding-15.7 {UtfToUtfProc emoji character output} { - set x 😂 - set y [encoding convertto utf-8 😂] - binary scan $y H* z - list [string length $y] $z -} {4 f09f9882} -test encoding-15.8 {UtfToUtfProc emoji character output} { +test encoding-15.6 {UtfToUtfProc emoji character output} { set x \uDE02\uD83D\uDE02\uD83D set y [encoding convertto utf-8 \uDE02\uD83D\uDE02\uD83D] binary scan $y H* z list [string length $y] $z } {10 efbfbdf09f9882efbfbd} -test encoding-15.9 {UtfToUtfProc emoji character output} { +test encoding-15.7 {UtfToUtfProc emoji character output} { set x \uDE02\uD83D\uD83D set y [encoding convertto utf-8 \uDE02\uD83D\uD83D] binary scan $y H* z list [string length $x] [string length $y] $z } {3 9 efbfbdefbfbdefbfbd} -test encoding-15.10 {UtfToUtfProc emoji character output} { - set x \uDE02\uD83D\xE9 - set y [encoding convertto utf-8 \uDE02\uD83D\xE9] +test encoding-15.8 {UtfToUtfProc emoji character output} { + set x \uDE02\uD83Dé + set y [encoding convertto utf-8 \uDE02\uD83Dé] binary scan $y H* z list [string length $x] [string length $y] $z } {3 8 efbfbdefbfbdc3a9} -test encoding-15.11 {UtfToUtfProc emoji character output} { +test encoding-15.9 {UtfToUtfProc emoji character output} { set x \uDE02\uD83DX set y [encoding convertto utf-8 \uDE02\uD83DX] binary scan $y H* z list [string length $x] [string length $y] $z } {3 7 efbfbdefbfbd58} -test encoding-15.12 {UtfToUtfProc high surrogate character output} { - set x \uDE02\xE9 - set y [encoding convertto utf-8 \uDE02\xE9] +test encoding-15.10 {UtfToUtfProc high surrogate character output} { + set x \uDE02é + set y [encoding convertto utf-8 \uDE02é] binary scan $y H* z list [string length $x] [string length $y] $z } {2 5 efbfbdc3a9} -test encoding-15.13 {UtfToUtfProc low surrogate character output} { - set x \uDA02\xE9 - set y [encoding convertto utf-8 \uDA02\xE9] +test encoding-15.11 {UtfToUtfProc low surrogate character output} { + set x \uDA02é + set y [encoding convertto utf-8 \uDA02é] binary scan $y H* z list [string length $x] [string length $y] $z } {2 5 efbfbdc3a9} -test encoding-15.14 {UtfToUtfProc high surrogate character output} { +test encoding-15.12 {UtfToUtfProc high surrogate character output} { set x \uDE02Y set y [encoding convertto utf-8 \uDE02Y] binary scan $y H* z list [string length $x] [string length $y] $z } {2 4 efbfbd59} -test encoding-15.15 {UtfToUtfProc low surrogate character output} { +test encoding-15.13 {UtfToUtfProc low surrogate character output} { set x \uDA02Y set y [encoding convertto utf-8 \uDA02Y] binary scan $y H* z list [string length $x] [string length $y] $z } {2 4 efbfbd59} -test encoding-15.16 {UtfToUtfProc high surrogate character output} { +test encoding-15.14 {UtfToUtfProc high surrogate character output} { set x \uDE02 set y [encoding convertto utf-8 \uDE02] binary scan $y H* z list [string length $x] [string length $y] $z } {1 3 efbfbd} -test encoding-15.17 {UtfToUtfProc low surrogate character output} { +test encoding-15.15 {UtfToUtfProc low surrogate character output} { set x \uDA02 set y [encoding convertto utf-8 \uDA02] binary scan $y H* z list [string length $x] [string length $y] $z } {1 3 efbfbd} +test encoding-15.16 {UtfToUtfProc: Invalid 4-byte UTF-8, see [ed29806ba]} { + set x \xF0\xA0\xA1\xC2 + set y [encoding convertfrom utf-8 \xF0\xA0\xA1\xC2] + list [string length $x] $y +} "4 \xF0\xA0\xA1\xC2" +test encoding-15.17 {UtfToUtfProc emoji character output} { + set x 😂 + set y [encoding convertto utf-8 😂] + binary scan $y H* z + list [string length $y] $z +} {4 f09f9882} test encoding-15.18 {UtfToUtfProc CESU-8 6-byte sequence} { set y [encoding convertto cesu-8 \U10000] binary scan $y H* z -- cgit v0.12 From 3bfe6dba24b7c5f8f9f8669cb99e2bfb80b0e5da Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 19 Apr 2021 07:24:18 +0000 Subject: More testcases (cesu-8) --- tests/encoding.test | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/encoding.test b/tests/encoding.test index e1c55d7..0e80c09 100644 --- a/tests/encoding.test +++ b/tests/encoding.test @@ -414,6 +414,21 @@ test encoding-15.18 {UtfToUtfProc CESU-8 6-byte sequence} { binary scan $y H* z list [string length $y] $z } {6 eda080edb080} +test encoding-15.19 {UtfToUtfProc CESU-8 upper surrogate} { + set y [encoding convertto cesu-8 \uD800] + binary scan $y H* z + list [string length $y] $z +} {3 eda080} +test encoding-15.20 {UtfToUtfProc CESU-8 lower surrogate} { + set y [encoding convertto cesu-8 \uDC00] + binary scan $y H* z + list [string length $y] $z +} {3 edb080} +test encoding-15.21 {UtfToUtfProc CESU-8 noncharacter} { + set y [encoding convertto cesu-8 \uFFFF] + binary scan $y H* z + list [string length $y] $z +} {3 efbfbf} test encoding-16.1 {Utf16ToUtfProc} -body { set val [encoding convertfrom utf-16 NN] -- cgit v0.12 From aa6c33b39d6959069884cf1ff5dbbebb731b33f1 Mon Sep 17 00:00:00 2001 From: dkf Date: Fri, 23 Apr 2021 20:24:00 +0000 Subject: Start of documenting our reference count management --- doc/AddErrInfo.3 | 16 ++++++++++++++++ doc/BoolObj.3 | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/doc/AddErrInfo.3 b/doc/AddErrInfo.3 index 5b0fe5a..d04b4c9 100644 --- a/doc/AddErrInfo.3 +++ b/doc/AddErrInfo.3 @@ -308,6 +308,22 @@ The global variables \fBerrorInfo\fR and \fBerrorCode\fR are not modified by \fBTcl_ResetResult\fR so they continue to hold a record of information about the most recent error seen in an interpreter. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The result of \fBTcl_GetReturnOptions\fR will have at least one +reference to it from the Tcl interpreter. If not using it immediately, +you should use \fBTcl_IncrRefCount\fR to add your own reference. +.PP +The \fIoptions\fR argument to \fBTcl_SetReturnOptions\fR will have a +reference added by the Tcl interpreter; it may safely be called with a +zero-reference value. +.PP +\fBTcl_AppendObjToErrorInfo\fR only reads its \fIobjPtr\fR argument; +it does not modify its reference count at all. +.PP +The \fIerrorObjPtr\fR argument to \fBTcl_SetObjErrorCode\fR will have a +reference added by the Tcl interpreter; it may safely be called with a +zero-reference value. .SH "SEE ALSO" Tcl_DecrRefCount(3), Tcl_IncrRefCount(3), Tcl_Interp(3), Tcl_ResetResult(3), Tcl_SetErrno(3), errorCode(n), errorInfo(n) diff --git a/doc/BoolObj.3 b/doc/BoolObj.3 index 7268e1f..9bbdc7e 100644 --- a/doc/BoolObj.3 +++ b/doc/BoolObj.3 @@ -88,6 +88,18 @@ will lead to a \fBTCL_OK\fR return (and the boolean value 1), while the same value passed to \fBTcl_GetBoolean\fR will lead to a \fBTCL_ERROR\fR return. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_NewBooleanObj\fR always returns a zero-reference object, much +like \fBTcl_NewObj\fR. +.PP +\fBTcl_SetBooleanObj\fR does not modify the reference count of its +\fIobjPtr\fR argument, but does require that the object be unshared. +.PP +\fBTcl_GetBooleanFromObj\fR does not modify the reference count of its +\fIobjPtr\fR argument; it only reads. Note however that this function +may set the interpreter result; if that is the only place that +is holding a reference to the object, it will be deleted. .SH "SEE ALSO" Tcl_NewObj, Tcl_IsShared, Tcl_GetBoolean -- cgit v0.12 From 103d88f85a9ade51cda8d153915a204bede470a3 Mon Sep 17 00:00:00 2001 From: dkf Date: Sat, 24 Apr 2021 09:04:19 +0000 Subject: Documenting our reference count management --- doc/ByteArrObj.3 | 12 ++++++++++++ doc/Cancel.3 | 8 ++++++++ doc/CrtAlias.3 | 10 +++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/doc/ByteArrObj.3 b/doc/ByteArrObj.3 index 09400c8..2a7d7a3 100644 --- a/doc/ByteArrObj.3 +++ b/doc/ByteArrObj.3 @@ -85,6 +85,18 @@ newly allocated bytes at the end of the array have arbitrary values. If the length of array is reduced to the new length. The return value is a pointer to the value's new array of bytes. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_NewByteArrayObj\fR always returns a zero-reference object, much +like \fBTcl_NewObj\fR. +.PP +\fBTcl_SetByteArrayObj\fR and \fBTcl_SetByteArrayLength\fR do not modify the +reference count of their \fIobjPtr\fR arguments, but do require that the +object be unshared. +.PP +\fBTcl_GetByteArrayFromObj\fR does not modify the reference count of its +\fIobjPtr\fR argument; it only reads. + .SH "SEE ALSO" Tcl_GetStringFromObj, Tcl_NewObj, Tcl_IncrRefCount, Tcl_DecrRefCount diff --git a/doc/Cancel.3 b/doc/Cancel.3 index 847707e..73edaf6 100644 --- a/doc/Cancel.3 +++ b/doc/Cancel.3 @@ -67,6 +67,14 @@ other procedures. If an error is returned and this bit is set in result, where it can be retrieved with \fBTcl_GetObjResult\fR or \fBTcl_GetStringResult\fR. If this flag bit is not set then no error message is left and the interpreter's result will not be modified. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_CancelEval\fR always decrements the reference count of its +\fIresultObjPtr\fR argument (if that is non-NULL). It is expected to +be usually called with an object with zero reference count. If the +object is shared with some other location (including the Tcl +evaluation stack) it should have its reference count incremented +before calling this function. .SH "SEE ALSO" interp(n), Tcl_Eval(3), TIP 285 diff --git a/doc/CrtAlias.3 b/doc/CrtAlias.3 index 92f9b0c..2623dcd 100644 --- a/doc/CrtAlias.3 +++ b/doc/CrtAlias.3 @@ -243,8 +243,16 @@ any script evaluation mechanism will fail. .PP For a description of the Tcl interface to multiple interpreters, see \fIinterp(n)\fR. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_CreateAliasObj\fR increments the reference counts of the values +in its \fIobjv\fR argument. (That reference lasts the same length of +time as the owning alias.) +.PP +\fBTcl_GetAliasObj\fR returns (via its \fIobjvPtr\fR argument) a +pointer to values that it holds a reference to. .SH "SEE ALSO" -interp +interp(n) .SH KEYWORDS alias, command, exposed commands, hidden commands, interpreter, invoke, -- cgit v0.12 From bd778f8dd4af97b8a349a22cfc23e3a047618c8d Mon Sep 17 00:00:00 2001 From: dkf Date: Sat, 24 Apr 2021 11:24:23 +0000 Subject: Documenting our reference count management --- doc/CrtMathFnc.3 | 4 ++++ doc/CrtObjCmd.3 | 13 +++++++++++ doc/CrtTrace.3 | 9 ++++++++ doc/DictObj.3 | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/DoubleObj.3 | 12 ++++++++++ doc/Encoding.3 | 11 ++++++++++ 6 files changed, 116 insertions(+) diff --git a/doc/CrtMathFnc.3 b/doc/CrtMathFnc.3 index acceb5b..bb96fc9 100644 --- a/doc/CrtMathFnc.3 +++ b/doc/CrtMathFnc.3 @@ -156,6 +156,10 @@ pointed to by \fIargTypesPointer\fR. \fBTcl_ListMathFuncs\fR returns a Tcl value containing a list of all the math functions defined in the interpreter whose name matches \fIpattern\fR. The returned value has a reference count of zero. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_ListMathFuncs\fR always returns a zero-reference object, much +like \fBTcl_NewObj\fR. .SH "SEE ALSO" expr(n), info(n), Tcl_CreateObjCommand(3), Tcl_Free(3), Tcl_NewListObj(3) .SH KEYWORDS diff --git a/doc/CrtObjCmd.3 b/doc/CrtObjCmd.3 index 2cd9222..854c057 100644 --- a/doc/CrtObjCmd.3 +++ b/doc/CrtObjCmd.3 @@ -323,6 +323,19 @@ instead. The result from \fBTcl_GetCommandTypeName\fR will be exactly that string which was registered, and not a copy; use of a compile-time constant string is \fIstrongly recommended\fR. .VE "info cmdtype feature" +.SH "REFERENCE COUNT MANAGEMENT" +.PP +When the \fIproc\fR passed to \fBTcl_CreateObjCommand\fR is called, +the values in its \fIobjv\fR argument will have a reference count of +at least 1, with that guaranteed reference being from the Tcl +evaluation stack. You should not call \fBTcl_DecrRefCount\fR on any of +those values unless you call \fBTcl_IncrRefCount\fR on them first. +.PP +\fBTcl_GetCommandFullName\fR does not modify the reference count of its +\fIobjPtr\fR argument, but does require that the object be unshared. +.PP +\fBTcl_GetCommandFromObj\fR does not modify the reference count of its +\fIobjPtr\fR argument; it only reads. .SH "SEE ALSO" Tcl_CreateCommand(3), Tcl_ResetResult(3), Tcl_SetObjResult(3) .SH KEYWORDS diff --git a/doc/CrtTrace.3 b/doc/CrtTrace.3 index b1e6483..bf8587d 100644 --- a/doc/CrtTrace.3 +++ b/doc/CrtTrace.3 @@ -187,5 +187,14 @@ There is no way to be notified when the trace created by \fBTcl_CreateTrace\fR is deleted. There is no way for the \fIproc\fR associated with a call to \fBTcl_CreateTrace\fR to abort execution of \fIcommand\fR. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +When the \fIproc\fR passed to \fBTcl_CreateObjTrace\fR is called, +the values in its \fIobjv\fR argument will have a reference count of +at least 1, with that guaranteed reference being from the Tcl +evaluation stack. You should not call \fBTcl_DecrRefCount\fR on any of +those values unless you call \fBTcl_IncrRefCount\fR on them first. +.SH "SEE ALSO" +trace(n) .SH KEYWORDS command, create, delete, interpreter, trace diff --git a/doc/DictObj.3 b/doc/DictObj.3 index 2c111c4..0b4c1ca 100644 --- a/doc/DictObj.3 +++ b/doc/DictObj.3 @@ -190,6 +190,73 @@ path as this is easy to construct from repeated use of dictionaries are created for non-terminal keys where they do not already exist. With \fBTcl_DictObjRemoveKeyList\fR, all non-terminal keys must exist and have dictionaries as their values. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_NewDictObj\fR always returns a zero-reference object, much like +\fBTcl_NewObj\fR. +.PP +\fBTcl_DictObjPut\fR does not modify the reference count of its \fIdictPtr\fR +argument, but does require that the object be unshared. If +\fBTcl_DictObjPut\fR returns \fBTCL_ERROR\fR it does not manipulate any +reference counts; but if it returns \fBTCL_OK\fR then it definitely increments +the reference count of \fIvaluePtr\fR and may increment the reference count of +\fIkeyPtr\fR; the latter case happens exactly when the key did not previously +exist in the dictionary. Note however that this function may set the +interpreter result; if that is the only place that is holding a reference to +an object, it will be deleted. +.PP +\fBTcl_DictObjGet\fR only reads from its \fIdictPtr\fR and \fIkeyPtr\fR +arguments, and does not manipulate their reference counts at all. If the +\fIvaluePtrPtr\fR argument is not set to NULL (and the function doesn't return +\fBTCL_ERROR\fR), it will be set to a value with a reference count of at least +1, with a reference owned by the dictionary. Note however that this function +may set the interpreter result; if that is the only place that is holding a +reference to an object, it will be deleted. +.PP +\fBTcl_DictObjRemove\fR does not modify the reference count of its +\fIdictPtr\fR argument, but does require that the object be unshared. It does +not manipulate the reference count of its \fIkeyPtr\fR argument at all. Note +however that this function may set the interpreter result; if that is the only +place that is holding a reference to an object, it will be deleted. +.PP +\fBTcl_DictObjSize\fR does not modify the reference count of its \fIdictPtr\fR +argument; it only reads. Note however that this function may set the +interpreter result; if that is the only place that is holding a reference to +the dictionary object, it will be deleted. +.PP +\fBTcl_DictObjFirst\fR does not modify the reference count of its +\fIdictPtr\fR argument; it only reads. The variables given by the +\fIkeyPtrPtr\fR and \fIvaluePtrPtr\fR arguments (if not NULL) will be updated +to contain references to the relevant values in the dictionary; their +reference counts will be at least 1 (due to the dictionary holding a reference +to them). It may also manipulate internal references; these are not exposed to +user code, but require a matching \fBTcl_DictObjDone\fR call. Note however +that this function may set the interpreter result; if that is the only place +that is holding a reference to the dictionary object, it will be deleted. +.PP +Similarly for \fBTcl_DictObjNext\fR; the variables given by the +\fIkeyPtrPtr\fR and \fIvaluePtrPtr\fR arguments (if not NULL) will be updated +to contain references to the relevant values in the dictionary; their +reference counts will be at least 1 (due to the dictionary holding a reference +to them). +.PP +\fBTcl_DictObjDone\fR does not manipulate (user-visible) reference counts. +.PP +\fBTcl_DictObjPutKeyList\fR is similar to \fBTcl_DictObjPut\fR; it does not +modify the reference count of its \fIdictPtr\fR argument, but does require +that the object be unshared. It may increment the reference count of any value +passed in the \fIkeyv\fR argument, and will increment the reference count of +the \fIvaluePtr\fR argument on success. It is recommended that values passed +via \fIkeyv\fR and \fIvaluePtr\fR do not have zero reference counts. Note +however that this function may set the interpreter result; if that is the only +place that is holding a reference to an object, it will be deleted. +.PP +\fBTcl_DictObjRemoveKeyList\fR is similar to \fBTcl_DictObjRemove\fR; it does +not modify the reference count of its \fIdictPtr\fR argument, but does require +that the object be unshared, and does not modify the reference counts of any +of the values passed in the \fIkeyv\fR argument. Note however that this +function may set the interpreter result; if that is the only place that is +holding a reference to an object, it will be deleted. .SH EXAMPLE Using the dictionary iteration interface to search determine if there is a key that maps to itself: diff --git a/doc/DoubleObj.3 b/doc/DoubleObj.3 index 85e4de5..c70f5d1 100644 --- a/doc/DoubleObj.3 +++ b/doc/DoubleObj.3 @@ -58,6 +58,18 @@ and if \fIinterp\fR is non-NULL, an error message is left in \fIinterp\fR. The \fBTcl_ObjType\fR of \fIobjPtr\fR may be changed to make subsequent calls to \fBTcl_GetDoubleFromObj\fR more efficient. '\" TODO: add discussion of treatment of NaN value +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_NewDoubleObj\fR always returns a zero-reference object, much +like \fBTcl_NewObj\fR. +.PP +\fBTcl_SetDoubleObj\fR does not modify the reference count of its +\fIobjPtr\fR argument, but does require that the object be unshared. +.PP +\fBTcl_GetDoubleFromObj\fR does not modify the reference count of its +\fIobjPtr\fR argument; it only reads. Note however that this function +may set the interpreter result; if that is the only place that +is holding a reference to the object, it will be deleted. .SH "SEE ALSO" Tcl_NewObj, Tcl_DecrRefCount, Tcl_IncrRefCount, Tcl_GetObjResult .SH KEYWORDS diff --git a/doc/Encoding.3 b/doc/Encoding.3 index 2d2461e..c68070c 100644 --- a/doc/Encoding.3 +++ b/doc/Encoding.3 @@ -556,5 +556,16 @@ been loaded, it attempts to load an encoding file called \fIname\fB.enc\fR from the \fBencoding\fR subdirectory of each directory that Tcl searches for its script library. If the encoding file exists, but is malformed, an error message will be left in \fIinterp\fR. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_GetEncodingFromObj\fR does not modify the reference count of its +\fIobjPtr\fR argument; it only reads. Note however that this function may set +the interpreter result; if that is the only place that is holding a reference +to the object, it will be deleted. +.PP +\fBTcl_GetEncodingSearchPath\fR returns an object with a reference count of at +least 1. +.SH "SEE ALSO" +encoding(n) .SH KEYWORDS utf, encoding, convert -- cgit v0.12 From ae32db3ffacfa9f981fd259a0feca4f3170eb8bb Mon Sep 17 00:00:00 2001 From: dkf Date: Sat, 24 Apr 2021 12:51:07 +0000 Subject: Documenting our reference count management --- doc/Ensemble.3 | 21 +++++++++++++++++++++ doc/Eval.3 | 14 ++++++++++++++ doc/ExprLongObj.3 | 9 +++++++++ 3 files changed, 44 insertions(+) diff --git a/doc/Ensemble.3 b/doc/Ensemble.3 index febc48f..b768fd6 100644 --- a/doc/Ensemble.3 +++ b/doc/Ensemble.3 @@ -209,6 +209,27 @@ namespace whose list of exported commands is used if both the mapping dictionary and the subcommand list properties are NULL. May be read using \fBTcl_GetEnsembleNamespace\fR which returns a Tcl result code (\fBTCL_OK\fR, or \fBTCL_ERROR\fR if the token does not refer to an ensemble). +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_FindEnsemble\fR does not modify the reference count of its +\fIcmdNameObj\fR argument; it only reads. Note however that this function may +set the interpreter result; if that is the only place that is holding a +reference to the object, it will be deleted. +.PP +The ensemble property getters (\fBTcl_GetEnsembleMappingDict\fR, +\fBTcl_GetEnsembleParameterList\fR, \fBTcl_GetEnsembleSubcommandList\fR, and +\fBTcl_GetEnsembleUnknownHandler\fR) do not manipulate the reference count of +the values they provide out; if those are non-NULL, they will have a reference +count of at least 1. Note that these functions may set the interpreter +result. +.PP +The ensemble property setters (\fBTcl_SetEnsembleMappingDict\fR, +\fBTcl_SetEnsembleParameterList\fR, \fBTcl_SetEnsembleSubcommandList\fR, and +\fBTcl_SetEnsembleUnknownHandler\fR) will increment the reference count of the +new value of the property they are given if they succeed (and decrement the +reference count of the old value of the property, if relevant). If the +property setters return \fBTCL_ERROR\fR, the reference count of the Tcl_Obj +argument is left unchanged. .SH "SEE ALSO" namespace(n), Tcl_DeleteCommandFromToken(3) .SH KEYWORDS diff --git a/doc/Eval.3 b/doc/Eval.3 index 1abe6f2..2769595 100644 --- a/doc/Eval.3 +++ b/doc/Eval.3 @@ -207,6 +207,20 @@ the \fBreturn\fR, \fBbreak\fR, or \fBcontinue\fR command was invoked in an inappropriate place. This means that top-level applications should never see a return code from \fBTcl_EvalObjEx\fR other than \fBTCL_OK\fR or \fBTCL_ERROR\fR. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_EvalObjEx\fR and \fBTcl_GlobalEvalObj\fR both increment and +decrement the reference count of their \fIobjPtr\fR argument; you must +not pass them any value with a reference count of zero. They also +manipulate the interpreter result; you must not count on the +interpreter result to hold the reference count of any value over +these calls. +.PP +\fBTcl_EvalObjv\fR may increment and decrement the reference count of +any value passed via its \fIobjv\fR argument; you must not pass any +value with a reference count of zero. This function also manipulates +the interpreter result; you must not count on the interpreter result +to hold the reference count of any value over this call. .SH KEYWORDS execute, file, global, result, script, value diff --git a/doc/ExprLongObj.3 b/doc/ExprLongObj.3 index 837e0a8..59413e1 100644 --- a/doc/ExprLongObj.3 +++ b/doc/ExprLongObj.3 @@ -98,6 +98,15 @@ containing the expression's value at \fI*resultPtrPtr\fR. In this case, the caller is responsible for calling \fBTcl_DecrRefCount\fR to decrement the value's reference count when it is finished with the value. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_ExprLongObj\fR, \fBTcl_ExprDoubleObj\fR, +\fBTcl_ExprBooleanObj\fR, and \fBTcl_ExprObj\fR all increment and +decrement the reference count of their \fIobjPtr\fR arguments; you +must not pass them any value with a reference count of zero. They also +manipulate the interpreter result; you must not count on the +interpreter result to hold the reference count of any value over these +calls. .SH "SEE ALSO" Tcl_ExprLong, Tcl_ExprDouble, Tcl_ExprBoolean, Tcl_ExprString, Tcl_GetObjResult -- cgit v0.12 From c1a1d841e7daf2695d727a24670542826ba6ac23 Mon Sep 17 00:00:00 2001 From: dkf Date: Sat, 24 Apr 2021 20:07:57 +0000 Subject: Documenting our reference count management --- doc/FileSystem.3 | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++- generic/tclIOUtil.c | 28 ++++----- 2 files changed, 176 insertions(+), 16 deletions(-) diff --git a/doc/FileSystem.3 b/doc/FileSystem.3 index 0a3aeef..4e901e0 100644 --- a/doc/FileSystem.3 +++ b/doc/FileSystem.3 @@ -45,7 +45,7 @@ int \fBTcl_FSDeleteFile\fR(\fIpathPtr\fR) .sp int -\fBTcl_FSRemoveDirectory\fR(\fIpathPtr, int recursive, errorPtr\fR) +\fBTcl_FSRemoveDirectory\fR(\fIpathPtr, recursive, errorPtr\fR) .sp int \fBTcl_FSRenameFile\fR(\fIsrcPathPtr, destPathPtr\fR) @@ -79,10 +79,10 @@ int \fBTcl_FSUtime\fR(\fIpathPtr, tval\fR) .sp int -\fBTcl_FSFileAttrsGet\fR(\fIinterp, int index, pathPtr, objPtrRef\fR) +\fBTcl_FSFileAttrsGet\fR(\fIinterp, index, pathPtr, objPtrRef\fR) .sp int -\fBTcl_FSFileAttrsSet\fR(\fIinterp, int index, pathPtr, Tcl_Obj *objPtr\fR) +\fBTcl_FSFileAttrsSet\fR(\fIinterp, index, pathPtr, objPtr\fR) .sp const char *const * \fBTcl_FSFileAttrStrings\fR(\fIpathPtr, objPtrRef\fR) @@ -197,6 +197,8 @@ rename operation. .AP Tcl_Obj *destPathPtr in As for \fIpathPtr\fR, but used for the destination filename for a copy or rename operation. +.AP int recursive in +Whether to remove subdirectories and their contents as well. .AP "const char" *encodingName in The encoding of the data stored in the file identified by \fIpathPtr\fR and to be evaluated. @@ -224,6 +226,10 @@ be joined together. If negative, then all elements are joined. .AP Tcl_Obj **errorPtr out In the case of an error, filled with a value containing the name of the file which caused an error in the various copy/rename operations. +.AP int index in +The index of the attribute in question. +.AP Tcl_Obj *objPtr in +The value to set in the operation. .AP Tcl_Obj **objPtrRef out Filled with a value containing the result of the operation. .AP Tcl_Obj *resultPtr out @@ -1628,6 +1634,158 @@ typedef int \fBTcl_FSChdirProc\fR( The \fBTcl_FSChdirProc\fR changes the applications current working directory to the value specified in \fIpathPtr\fR. The function returns -1 on error or 0 on success. +.SH "REFERENCE COUNT MANAGEMENT" +.SS "PUBLIC API CALLS" +.PP +For all of these functions, \fIpathPtr\fR (including the \fIsrcPathPtr\fR and +\fIdestPathPtr\fR arguments to \fBTcl_FSCopyFile\fR, +\fBTcl_FSCopyDirectory\fR, and \fBTcl_FSRenameFile\fR, the \fIfirstPtr\fR and +\fIsecondPtr\fR arguments to \fBTcl_FSEqualPaths\fR, and the \fIlinkNamePtr\fR +and \fItoPtr\fR arguments to \fBTcl_FSLink\fR) must not be a zero reference +count value; references may be retained in internal caches even for +theoretically read-only operations. These functions may also manipulate the +interpreter result (if they take and are given a non-NULL \fIinterp\fR +argument); you must not count on the interpreter result to hold the reference +count of any argument value over these calls and should manage your own +references there. However, references held by the arguments to a Tcl command +\fIare\fR suitable for reference count management purposes for the duration of +the implementation of that command. +.PP +The \fIerrorPtr\fR argument to \fBTcl_FSCopyDirectory\fR and +\fBTcl_FSRemoveDirectory\fR is, when an object is set into it at all, set to +an object with a non-zero reference count that should be passed to +\fBTcl_DecrRefCount\fR when no longer needed. +.PP +\fBTcl_FSListVolumes\fR always returns a zero-reference object, much +like \fBTcl_NewObj\fR. +.PP +\fBTcl_FSLink\fR always returns a non-zero-reference object when it is +asked to read; you must call \fBTcl_DecrRefCount\fR on the object +once you no longer need it. +.PP +\fBTcl_FSGetCwd\fR always returns a non-zero-reference object; you +must call \fBTcl_DecrRefCount\fR on the object once you no longer need +it. +.PP +\fBTcl_FSPathSeparator\fR always returns a zero-reference object, much +like \fBTcl_NewObj\fR. +.PP +\fBTcl_FSJoinPath\fR always returns a zero-reference object, much +like \fBTcl_NewObj\fR. Its \fIlistObj\fR argument can have any reference +count; it is only read by this function. +.PP +\fBTcl_FSSplitPath\fR always returns a zero-reference object, much +like \fBTcl_NewObj\fR. +.PP +\fBTcl_FSGetNormalizedPath\fR returns an object with a non-zero +reference count where Tcl is the owner. You should increment its +reference count if you want to retain it, but do not need to if you +are just using the value immediately. +.PP +\fBTcl_FSJoinToPath\fR always returns a zero-reference object, much like +\fBTcl_NewObj\fR. Its \fIbasePtr\fR argument follows the rules above for +\fIpathPtr\fR, as do the values in the \fIobjv\fR argument. +.PP +\fBTcl_FSGetTranslatedPath\fR returns a non-zero-reference object (or +NULL in the error case); you must call \fBTcl_DecrRefCount\fR on the +object once you no longer need it. +.PP +\fBTcl_FSNewNativePath\fR always returns a zero-reference object (or +NULL), much like \fBTcl_NewObj\fR. +.PP +\fBTcl_FSFileSystemInfo\fR always returns a zero-reference object (or +NULL), much like \fBTcl_NewObj\fR. +.PP +The \fIobjPtr\fR and \fIobjPtrRef\fR arguments to \fBTcl_FSFileAttrsGet\fR, +\fBTcl_FSFileAttrsSet\fR and \fBTcl_FSFileAttrStrings\fR are conventional Tcl +values; the \fIobjPtr\fR argument will be read but not retained, and the +\fIobjPtrRef\fR argument will have (on success) a zero-reference value written +into it (as with \fBTcl_NewObj\fR). \fBTcl_FSFileAttrsGet\fR and +\fBTcl_FSFileAttrsSet\fR may also manipulate the interpreter result. +.PP +The \fIresultPtr\fR argument to \fBTcl_FSMatchInDirectory\fR will not have its +reference count manipulated, but it should have a reference count of no more +than 1, and should not be the current interpreter result (as the function may +overwrite that on error). +.SS "VIRTUAL FILESYSTEM INTERFACE" +.PP +For all virtual filesystem implementation functions, any \fIpathPtr\fR +arguments should not have their reference counts manipulated. If they take an +\fIinterp\fR argument, they may set an error message in that, but must not +manipulate the \fIpathPtr\fR afterwards. Aside from that: +.TP +\fIinternalToNormalizedProc\fR +. +This should return a zero-reference count value, as if allocated with +\fBTcl_NewObj\fR. +.TP +\fInormalizePathProc\fR +. +Unlike with other API implementation functions, the \fIpathPtr\fR argument +here is guaranteed to be an unshared object that should be updated. Its +reference count should not be modified. +.TP +\fIfilesystemPathTypeProc\fR +. +The return value (if non-NULL) either has a reference count of zero or needs +to be maintained (on a per-thread basis) by the filesystem. Tcl will increment +the reference count of the value if it wishes to retain it. +.TP +\fIfilesystemSeparatorProc\fR +. +The return value should be a value with reference count of zero. +.TP +\fImatchInDirectoryProc\fR +. +The \fIresultPtr\fR argument should be assumed to hold a list that can be +appended to (i.e., that has a reference count no greater than 1). No reference +to it should be retained. +.TP +\fIlinkProc\fR +. +If \fItoPtr\fR is NULL, this should return a value with reference count 1 that +has just been allocated and passed to \fBTcl_IncrRefCount\fR. If \fItoPtr\fR +is not NULL, it should be returned on success. +.TP +\fIlistVolumesProc\fR +. +The result value should be a list (if non-NULL); it will have its reference +count decremented once (with \fBTcl_DecrRefCount\fR) by Tcl once done. +.TP +\fIfileAttrStringsProc\fR +. +If the result is NULL, the \fIobjPtrRef\fR should have a list value written to +it; that list will have its reference count both incremented (with +\fBTcl_IncrRefCount\fR) and decremented (with \fBTcl_DecrRefCount\fR). +.TP +\fIfileAttrsGetProc\fR +. +The \fIobjPtrRef\fR argument should have (on non-error return) a zero +reference count value written to it (allocated as if with \fBTcl_NewObj\fR). +.TP +\fIfileAttrsSetProc\fR +. +The \fIobjPtr\fR argument should either just be read or its reference count +incremented to retain it. +.TP +\fIremoveDirectoryProc\fR +. +If an error is being reported, the problem filename reported via +\fIerrorPtr\fR should be newly allocated (as if with \fBTcl_NewObj\fR) and +have a reference count of 1 (i.e., have been passed to +\fBTcl_IncrRefCount\fR). +.TP +\fIcopyDirectoryProc\fR +. +If an error is being reported, the problem filename reported via +\fIerrorPtr\fR should be newly allocated (as if with \fBTcl_NewObj\fR) and +have a reference count of 1 (i.e., have been passed to +\fBTcl_IncrRefCount\fR). +.TP +\fIgetCwdProc\fR +. +The result will be passed to \fBTcl_DecrRefCount\fR by the implementation of +\fBTcl_FSGetCwd\fR after it has been normalized. .SH "SEE ALSO" cd(n), file(n), filename(n), load(n), open(n), pwd(n), source(n), unload(n) .SH KEYWORDS diff --git a/generic/tclIOUtil.c b/generic/tclIOUtil.c index 698b614..87e60c3 100644 --- a/generic/tclIOUtil.c +++ b/generic/tclIOUtil.c @@ -2646,8 +2646,9 @@ Tcl_FSGetCwd( norm = TclFSNormalizeAbsolutePath(interp,retVal); if (norm != NULL) { /* - * Assign to global storage the pathname of the current directory - * and copy it into thread-local storage as well. + * Assign to global storage the pathname of the current + * directory and copy it into thread-local storage as + * well. * * At system startup multiple threads could in principle * call this function simultaneously, which is a little @@ -3789,10 +3790,12 @@ Tcl_FSListVolumes(void) if (thisFsVolumes != NULL) { Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes); - /* The refCount of each list returned by a `listVolumesProc` is - * already incremented. Do not hang onto the list, though. It - * belongs to the filesystem. Add its contents to * the result - * we are building, and then decrement the refCount. */ + /* + * The refCount of each list returned by a `listVolumesProc` + * is already incremented. Do not hang onto the list, though. + * It belongs to the filesystem. Add its contents to the + * result we are building, and then decrement the refCount. + */ Tcl_DecrRefCount(thisFsVolumes); } } @@ -4365,15 +4368,14 @@ Tcl_FSCreateDirectory( int Tcl_FSCopyDirectory( - Tcl_Obj *srcPathPtr, /* - * The pathname of the directory to be copied. - */ + Tcl_Obj *srcPathPtr, /* The pathname of the directory to be + * copied. */ Tcl_Obj *destPathPtr, /* The pathname of the target directory. */ Tcl_Obj **errorPtr) /* If not NULL, and there is an error, a place - * to store a pointer to a new object, with - * its refCount already incremented, and - * containing the pathname name of file - * causing the error. */ + * to store a pointer to a new object, with + * its refCount already incremented, and + * containing the pathname name of file + * causing the error. */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; -- cgit v0.12 From 8a32e272a433d0d1c70502e85cff49464d5e50ce Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 25 Apr 2021 07:44:59 +0000 Subject: Documenting our reference count management --- doc/GetIndex.3 | 6 ++++++ doc/Hash.3 | 14 ++++++++++++++ doc/IntObj.3 | 21 +++++++++++++++++++++ doc/ListObj.3 | 25 +++++++++++++++++++++++++ doc/Load.3 | 4 ++++ 5 files changed, 70 insertions(+) diff --git a/doc/GetIndex.3 b/doc/GetIndex.3 index 8591c56..a788848 100644 --- a/doc/GetIndex.3 +++ b/doc/GetIndex.3 @@ -105,6 +105,12 @@ array of characters at \fItablePtr\fR+\fIoffset\fR bytes, etc.) This is particularly useful when processing things like \fBTk_ConfigurationSpec\fR, whose string keys are in the same place in each of several array elements. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_GetIndexFromObj\fR and \fBTcl_GetIndexFromObjStruct\fR do not modify +the reference count of their \fIobjPtr\fR arguments; they only read. Note +however that these functions may set the interpreter result; if that is the +only place that is holding a reference to the object, it will be deleted. .SH "SEE ALSO" prefix(n), Tcl_WrongNumArgs(3) .SH KEYWORDS diff --git a/doc/Hash.3 b/doc/Hash.3 index aa79b86..0532390 100644 --- a/doc/Hash.3 +++ b/doc/Hash.3 @@ -330,5 +330,19 @@ typedef void \fBTcl_FreeHashEntryProc\fR( If this is NULL then \fBTcl_Free\fR is used to free the space for the entry. Tcl_Obj* keys use this function to decrement the reference count on the value. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +When a hash table is created with \fBTcl_InitCustomHashTable\fR, the +\fBTcl_CreateHashEntry\fR function will increment the reference count of its +\fIkey\fR argument when it creates a key (but not if there is an existing +matching key). The reference count of the key will be decremented when the +corresponding hash entry is deleted, whether with \fBTcl_DeleteHashEntry\fR or +with \fBTcl_DeleteHashTable\fR. The \fBTcl_GetHashKey\fR function will return +the key without further modifying its reference count. +.PP +Custom hash tables that use a Tcl_Obj* as key will generally need to do +something similar in their \fIallocEntryProc\fR. +.SH "SEE ALSO" +Dict(3) .SH KEYWORDS hash table, key, lookup, search, value diff --git a/doc/IntObj.3 b/doc/IntObj.3 index 36bfa7d..d640dbb 100644 --- a/doc/IntObj.3 +++ b/doc/IntObj.3 @@ -160,6 +160,27 @@ If anything later in the caller requires The \fBTcl_InitBignumFromDouble\fR routine is a utility procedure that extracts the integer part of \fIdoubleValue\fR and stores that integer value in the \fBmp_int\fR value \fIbigValue\fR. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_NewIntObj\fR, \fBTcl_NewLongObj\fR, \fBTcl_NewWideIntObj\fR, and +\fBTcl_NewBignumObj\fR always return a zero-reference object, much like +\fBTcl_NewObj\fR. +.PP +\fBTcl_SetIntObj\fR, \fBTcl_SetLongObj\fR, \fBTcl_SetWideIntObj\fR, and +\fBTcl_SetBignumObj\fR do not modify the reference count of their \fIobjPtr\fR +arguments, but do require that the object be unshared. +.PP +\fBTcl_GetIntFromObj\fR, \fBTcl_GetIntForIndex\fR, \fBTcl_GetLongFromObj\fR, +\fBTcl_GetWideIntFromObj\fR, \fBTcl_GetBignumFromObj\fR, and +\fBTcl_TakeBignumFromObj\fR do not modify the reference count of their +\fIobjPtr\fR arguments; they only read. Note however that this function may +set the interpreter result; if that is the only place that is holding a +reference to the object, it will be deleted. Also note that if +\fBTcl_TakeBignumFromObj\fR is given an unshared value, the value of that +object may be modified; it is intended to be used when the value is +.QW consumed +by the operation at this point. + .SH "SEE ALSO" Tcl_NewObj, Tcl_DecrRefCount, Tcl_IncrRefCount, Tcl_GetObjResult .SH KEYWORDS diff --git a/doc/ListObj.3 b/doc/ListObj.3 index ab836d8..67721c9 100644 --- a/doc/ListObj.3 +++ b/doc/ListObj.3 @@ -246,6 +246,31 @@ with a NULL \fIobjvPtr\fR: result = \fBTcl_ListObjReplace\fR(interp, listPtr, first, count, 0, NULL); .CE +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_NewListObj\fR always returns a zero-reference object, much like +\fBTcl_NewObj\fR. If a non-NULL \fIobjv\fR argument is given, the reference +counts of the first \fIobjc\fR values in that array are incremented. +.PP +\fBTcl_SetListObj\fR does not modify the reference count of its \fIobjPtr\fR +argument, but does require that the object be unshared. The reference counts +of the first \fIobjc\fR values in the \fIobjv\fR array are incremented. +.PP +\fBTcl_ListObjGetElements\fR, \fBTcl_ListObjIndex\fR, and +\fBTcl_ListObjLength\fR do not modify the reference count of their +\fIlistPtr\fR arguments; they only read. Note however that these three +functions may set the interpreter result; if that is the only place that is +holding a reference to the object, it will be deleted. +.PP +\fBTcl_ListObjAppendList\fR, \fBTcl_ListObjAppendElement\fR, and +\fBTcl_ListObjReplace\fR require an unshared \fIlistPtr\fR argument. +\fBTcl_ListObjAppendList\fR only reads its \fIelemListPtr\fR argument. +\fBTcl_ListObjAppendElement\fR increments the reference count of its +\fIobjPtr\fR on success. \fBTcl_ListObjReplace\fR increments the reference +count of the first \fIobjc\fR values in the \fIobjv\fR array on success. Note +however that all these three functions may set the interpreter result on +failure; if that is the only place that is holding a reference to the object, +it will be deleted. .SH "SEE ALSO" Tcl_NewObj(3), Tcl_DecrRefCount(3), Tcl_IncrRefCount(3), Tcl_GetObjResult(3) .SH KEYWORDS diff --git a/doc/Load.3 b/doc/Load.3 index 1d0d738..4533510 100644 --- a/doc/Load.3 +++ b/doc/Load.3 @@ -60,6 +60,10 @@ be unloaded with \fBTcl_FSUnloadFile\fR. the symbol cannot be found, it returns NULL and sets an error message in the given \fIinterp\fR (if that is non-NULL). Note that it is unsafe to use this operation on a handle that has been passed to \fBTcl_FSUnloadFile\fR. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The reference count of the \fIpathPtr\fR argument to \fBTcl_LoadFile\fR may be +incremented. As such, it should not be given a zero reference count value. .SH "SEE ALSO" Tcl_FSLoadFile(3), Tcl_FSUnloadFile(3), load(n), unload(n) .SH KEYWORDS -- cgit v0.12 From 2453c29945269ab4734362d7613c187d3e6e8fbb Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 25 Apr 2021 10:56:32 +0000 Subject: Documenting our reference count management --- doc/Method.3 | 26 +++++++++++++++++++++++++- doc/NRE.3 | 19 +++++++++++++++++++ doc/Namespace.3 | 16 ++++++++++++++-- doc/Object.3 | 7 ++++++- doc/ObjectType.3 | 22 ++++++++++++++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/doc/Method.3 b/doc/Method.3 index 9e636a1..577cd54 100644 --- a/doc/Method.3 +++ b/doc/Method.3 @@ -258,8 +258,32 @@ also return TCL_ERROR; it should return TCL_OK otherwise. The \fIoldClientData\fR field to a Tcl_CloneProc gives the value from the method being copied from, and the \fInewClientDataPtr\fR field will point to a variable in which to write the value for the method being copied to. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fInameObj\fR argument to \fBTcl_NewMethod\fR and +\fBTcl_NewInstanceMethod\fR (when non-NULL) will have its reference count +incremented if there is no existing method with that name in that +class/object. +.PP +The result of \fBTcl_MethodName\fR is a value with a reference count of at +least one. It should not be modified without first duplicating it (with +\fBTcl_DuplicateObj\fR). +.PP +The values in the first \fIobjc\fR values of the \fIobjv\fR argument to +\fBTcl_ObjectContextInvokeNext\fR are assumed to have a reference count of at +least 1; the containing array is assumed to endure until the next method +implementation (see \fBnext\fR) returns. Be aware that methods may +\fByield\fR; if any post-call actions are desired (e.g., decrementing the +reference count of values passed in here), they must be scheduled with +\fBTcl_NRAddCallback\fR. +.PP +The \fIcallProc\fR of the \fBTcl_MethodType\fR structure takes values of at +least reference count 1 in its \fIobjv\fR argument. It may add its own +references, but must not decrement the reference count below that level; the +caller of the method will decrement the reference count once the method +returns properly (and the reference will be held if the method \fByield\fRs). .SH "SEE ALSO" -Class(3), oo::class(n), oo::define(n), oo::object(n) +Class(3), NRE(3), oo::class(n), oo::define(n), oo::object(n) .SH KEYWORDS constructor, method, object diff --git a/doc/NRE.3 b/doc/NRE.3 index 20efe2f..011e100 100644 --- a/doc/NRE.3 +++ b/doc/NRE.3 @@ -227,6 +227,25 @@ int Any function comprising a routine can push other functions, making it possible implement looping and sequencing constructs using the function stack. .PP +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The first \fIobjc\fR values in the \fIobjv\fR array passed to the functions +\fBTcl_NRCallObjProc\fR, \fBTcl_NREvalObjv\fR, and \fBTcl_NRCmdSwap\fR should +have a reference count of at least 1; they may have additional references +taken during the execution. +.PP +The \fIobjPtr\fR argument to \fBTcl_NREvalObj\fR and \fBTcl_NRExprObj\fR +should have a reference count of at least 1, and may have additional +references taken to it during execution. +.PP +The \fIresultObj\fR argument to \fBTcl_NRExprObj\fR should be an unshared +object. +.PP +Use \fBTcl_NRAddCallback\fR to schedule any required final decrementing of the +reference counts of arguments to any of the other functions on this page, as +with any other post-processing step in the non-recursive execution engine. +.PP +The .SH "SEE ALSO" Tcl_CreateCommand(3), Tcl_CreateObjCommand(3), Tcl_EvalObjEx(3), Tcl_GetCommandFromObj(3), Tcl_ExprObj(3) .SH KEYWORDS diff --git a/doc/Namespace.3 b/doc/Namespace.3 index a037442..49b772c 100644 --- a/doc/Namespace.3 +++ b/doc/Namespace.3 @@ -46,10 +46,10 @@ Tcl_Command \fBTcl_FindCommand\fR(\fIinterp, name, contextNsPtr, flags\fR) .sp Tcl_Obj * -\fBTcl_GetNamespaceUnknownHandler(\fIinterp, nsPtr\fR) +\fBTcl_GetNamespaceUnknownHandler\fR(\fIinterp, nsPtr\fR) .sp int -\fBTcl_SetNamespaceUnknownHandler(\fIinterp, nsPtr, handlerPtr\fR) +\fBTcl_SetNamespaceUnknownHandler\fR(\fIinterp, nsPtr, handlerPtr\fR) .SH ARGUMENTS .AS Tcl_NamespaceDeleteProc allowOverwrite in/out .AP Tcl_Interp *interp in/out @@ -159,6 +159,18 @@ for the namespace, or NULL if none is set. \fBTcl_SetNamespaceUnknownHandler\fR sets the unknown command handler for the namespace. If \fIhandlerPtr\fR is NULL, then the handler is reset to its default. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fIobjPtr\fR argument to \fBTcl_AppendExportList\fR should be an +unshared object, as it will be modified by this function. The +reference count of \fIobjPtr\fR will not be altered. +.PP +\fBTcl_GetNamespaceUnknownHandler\fR returns a possibly shared value. +Its reference count should be incremented if the value is to be +retained. +.PP +The \fIhandlerPtr\fR argument to \fBTcl_SetNamespaceUnknownHandler\fR +will have its reference count incremented if it is a non-empty list. .SH "SEE ALSO" Tcl_CreateCommand(3), Tcl_ListObjAppendList(3), Tcl_SetVar(3) .SH KEYWORDS diff --git a/doc/Object.3 b/doc/Object.3 index eadd041..2099552 100644 --- a/doc/Object.3 +++ b/doc/Object.3 @@ -283,7 +283,12 @@ to reduce storage requirements. Reference counting is used to determine when a value is no longer needed and can safely be freed. A value just created by \fBTcl_NewObj\fR or \fBTcl_NewStringObj\fR -has \fIrefCount\fR 0. +has \fIrefCount\fR 0, meaning that the object can often be given to a function +like \fBTcl_SetObjResult\fR, \fBTcl_ListObjAppendElement\fR, or +\fBTcl_DictObjPut\fR (as a value) without explicit reference management, all +of which are common use cases. (The latter two require that the the target +list or dictionary be well-formed, but that is often easy to arrange when the +value is being initially constructed.) The macro \fBTcl_IncrRefCount\fR increments the reference count when a new reference to the value is created. The macro \fBTcl_DecrRefCount\fR decrements the count diff --git a/doc/ObjectType.3 b/doc/ObjectType.3 index 67f5174..7e3cc12 100644 --- a/doc/ObjectType.3 +++ b/doc/ObjectType.3 @@ -248,6 +248,28 @@ The \fIfreeIntRepProc\fR implementation must not access the uses of that field during value deletion. The defined tasks for the \fIfreeIntRepProc\fR have no need to consult the \fIbytes\fR member. +.PP +Note that if a subsidiary value has its reference count reduced to zero +during the running of a \fIfreeIntRepProc\fR, that value may be not freed +immediately, in order to limit stack usage. However, the value will be freed +before the outermost current \fBTcl_DecrRefCount\fR returns. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fIobjPtr\fR argument to \fBTcl_AppendAllObjTypes\fR should be an unshared +value; this function will not modify the reference count of that value, but +will modify its contents. If \fIobjPtr\fR is not (interpretable as) a list, +this function will set the interpreter result and produce an error; using an +unshared empty value is strongly recommended. +.PP +The \fIobjPtr\fR argument to \fBTcl_ConvertToType\fR can have any non-zero +reference count; this function will not modify the reference count, but may +write to the interpreter result on error so values that originate from there +should have an additional reference made before calling this. +.PP +None of the callback functions in the \fBTcl_ObjType\fR structure should +modify the reference count of their arguments, but if the values contain +subsidiary values (e.g., the elements of a list or the keys of a dictionary) +then those subsidiary values may have their reference counts modified. .SH "SEE ALSO" Tcl_NewObj(3), Tcl_DecrRefCount(3), Tcl_IncrRefCount(3) .SH KEYWORDS -- cgit v0.12 From b63d1213ed575f5b179c1e9244ec808e2fe69b96 Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 25 Apr 2021 11:20:35 +0000 Subject: Documenting our reference count management --- doc/Class.3 | 23 +++++++++++++++++++++++ doc/OpenFileChnl.3 | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/doc/Class.3 b/doc/Class.3 index 57203d5..5f8e061 100644 --- a/doc/Class.3 +++ b/doc/Class.3 @@ -241,6 +241,29 @@ NULL if the whole chain is to be processed (the argument itself is never NULL); this variable may be updated by the callback. The \fImethodNameObj\fR parameter gives an unshared object containing the name of the method being invoked, as provided by the user; this object may be updated by the callback. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fIobjPtr\fR argument to \fBTcl_GetObjectFromObj\fR will not have its +reference count manipulated, but this function may modify the interpreter +result (to report any error) so interpreter results should not be fed into +this without an additional reference being used. +.PP +The result of \fBTcl_GetObjectName\fR is a value that is owned by the object +that is regenerated when this function is first called after the object is +renamed. If the value is to be retained at all, the caller should increment +the reference count. +.PP +The first \fIobjc\fR values in the \fIobjv\fR argument to +\fBTcl_NewObjectInstance\fR are the arguments to pass to the constructor. They +must have a reference count of at least 1, and may have their reference counts +changed during the running of the constructor. Constructors may modify the +interpreter result, which consequently means that interpreter results should +not be used as arguments without an additional reference being taken. +.PP +The \fImethodNameObj\fR argument to a Tcl_ObjectMapMethodNameProc +implementation will be a value with a reference count of at least 1 where at +least one reference is not held by the interpreter result. It is expected that +method name mappers will only read their \fImethodNameObj\fR arguments. .SH "SEE ALSO" Method(3), oo::class(n), oo::copy(n), oo::define(n), oo::object(n) .SH KEYWORDS diff --git a/doc/OpenFileChnl.3 b/doc/OpenFileChnl.3 index c1d1922..4e42b93 100644 --- a/doc/OpenFileChnl.3 +++ b/doc/OpenFileChnl.3 @@ -641,6 +641,24 @@ the channel was created with \fBTcl_OpenFileChannel\fR, \fBTcl_OpenCommandChannel\fR, or \fBTcl_MakeFileChannel\fR. Other channel types may return a different type of handle on Windows platforms. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fIreadObjPtr\fR argument to \fBTcl_ReadChars\fR must be an unshared +value; it will be modified by this function. Using the interpreter result for +this purpose is \fIstrongly\fR not recommended; the preferred pattern is to +use a new value from \fBTcl_NewObj\fR to receive the data and only to pass it +to \fBTcl_SetObjResult\fR if this function succeeds. +.PP +The \fIlineObjPtr\fR argument to \fBTcl_GetsObj\fR must be an unshared value; +it will be modified by this function. Using the interpreter result for this +purpose is \fIstrongly\fR not recommended; the preferred pattern is to use a +new value from \fBTcl_NewObj\fR to receive the data and only to pass it to +\fBTcl_SetObjResult\fR if this function succeeds. +.PP +The \fIwriteObjPtr\fR argument to \fBTcl_WriteObj\fR should be a value with +any reference count. This function will not modify the reference count. Using +the interpreter result without adding an additional reference to it is not +recommended. .SH "SEE ALSO" DString(3), fconfigure(n), filename(n), fopen(3), Tcl_CreateChannel(3) .SH KEYWORDS -- cgit v0.12 From 4aa680d80d061bebaf17ae938a541d6caff522cf Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 25 Apr 2021 12:01:10 +0000 Subject: Documenting our reference count management --- doc/ParseArgs.3 | 6 ++++++ doc/ParseCmd.3 | 8 ++++++++ doc/PkgRequire.3 | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/doc/ParseArgs.3 b/doc/ParseArgs.3 index def55de..5ca9fa5 100644 --- a/doc/ParseArgs.3 +++ b/doc/ParseArgs.3 @@ -189,6 +189,12 @@ will be stored at \fIdstPtr\fR; the string inside will have a lifetime linked to the lifetime of the string representation of the argument value that it came from, and so should be copied if it needs to be retained. The \fIsrcPtr\fR and \fIclientData\fR fields are ignored. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The values in the \fiobjv\fR argument to \fBTcl_ParseArgsObjv\fR will not have +their reference counts modified by this function. The interpreter result may +be modified on error; the values passed should not be the interpreter result +with no further reference added. .SH "SEE ALSO" Tcl_GetIndexFromObj(3), Tcl_Main(3), Tcl_CreateObjCommand(3) .SH KEYWORDS diff --git a/doc/ParseCmd.3 b/doc/ParseCmd.3 index 40a0818..03b97f7 100644 --- a/doc/ParseCmd.3 +++ b/doc/ParseCmd.3 @@ -191,6 +191,7 @@ code with one of the values \fBTCL_OK\fR, \fBTCL_ERROR\fR, some other integer value originating in an extension. In addition, a result value or error message is left in \fIinterp\fR's result; it can be retrieved using \fBTcl_GetObjResult\fR. +.SS "DEPRECATED FUNCTIONS" .PP \fBTcl_EvalTokens\fR differs from \fBTcl_EvalTokensStandard\fR only in the return convention used: it returns the result in a new Tcl_Obj. @@ -463,5 +464,12 @@ There are additional fields in the Tcl_Parse structure after the \fBTcl_ParseCommand\fR, \fBTcl_ParseExpr\fR, \fBTcl_ParseBraces\fR, \fBTcl_ParseQuotedString\fR, and \fBTcl_ParseVarName\fR; they should not be referenced by code outside of these procedures. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The result of \fBTcl_EvalTokens\fR is an unshared value with a reference count +of 1; the caller of that function should call \fBTcl_DecrRefCount\fR on the +result value to dispose of it. (The equivalent with +\fBTcl_EvalTokenStandard\fR is just the interpreter result, which can be +retrieved with \fBTcl_GetObjResult\fR.) .SH KEYWORDS backslash substitution, braces, command, expression, parse, token, variable substitution diff --git a/doc/PkgRequire.3 b/doc/PkgRequire.3 index f32c936..77e73f1 100644 --- a/doc/PkgRequire.3 +++ b/doc/PkgRequire.3 @@ -91,6 +91,10 @@ functions. \fBTcl_PkgRequireProc\fR is the form of \fBpackage require\fR handling multiple requirements. The other forms are present for backward compatibility and translate their invocations to this form. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The requirements values given (in the \fIobjv\fR argument) to +\fBTcl_PkgRequireProc\fR must have non-zero reference counts. .SH KEYWORDS package, present, provide, require, version .SH "SEE ALSO" -- cgit v0.12 From 5a5cb4c044353add5cfbc502d8fdfb34d4410af6 Mon Sep 17 00:00:00 2001 From: pooryorick Date: Sun, 25 Apr 2021 13:24:09 +0000 Subject: Fix for issue [f6fbc71cd160d779], with "make valgrind", safe-zipfs-5.5 fails with the error, 'can't set "path": variable is array' --- tests/safe-zipfs.test | 1393 +++++++++++++++++++++++++------------------------ 1 file changed, 699 insertions(+), 694 deletions(-) diff --git a/tests/safe-zipfs.test b/tests/safe-zipfs.test index ba6fe50..a6088c4 100644 --- a/tests/safe-zipfs.test +++ b/tests/safe-zipfs.test @@ -13,709 +13,714 @@ # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. -if {"::tcltest" ni [namespace children]} { - package require tcltest 2.5 - namespace import -force ::tcltest::* -} -foreach i [interp children] { - interp delete $i -} +apply [list {} { + global auto_path + global tcl_library + if {"::tcltest" ni [namespace children]} { + package require tcltest 2.5 + namespace import -force ::tcltest::* + } -set SaveAutoPath $::auto_path -set ::auto_path [info library] -set TestsDir [file normalize [file dirname [info script]]] + foreach i [interp children] { + interp delete $i + } -set ZipMountPoint [zipfs root]auto-files -zipfs mount $ZipMountPoint [file join $TestsDir auto-files.zip] + set SaveAutoPath $::auto_path + set ::auto_path [info library] + set TestsDir [file normalize [file dirname [info script]]] -set PathMapp {} -lappend PathMapp $tcl_library TCLLIB $TestsDir TESTSDIR $ZipMountPoint ZIPDIR + set ZipMountPoint [zipfs root]auto-files + zipfs mount $ZipMountPoint [file join $TestsDir auto-files.zip] -proc mapList {map listIn} { - set listOut {} - foreach element $listIn { - lappend listOut [string map $map $element] - } - return $listOut -} -proc mapAndSortList {map listIn} { - set listOut {} - foreach element $listIn { - lappend listOut [string map $map $element] - } - lsort $listOut -} - -# Force actual loading of the safe package because we use un-exported (and -# thus un-autoindexed) APIs in this test result arguments: -catch {safe::interpConfigure} - -# Tests 5.* test the example files before using them to test safe interpreters. - -test safe-zipfs-5.1 {example tclIndex commands, test in parent interpreter; zipfs} -setup { - set tmpAutoPath $::auto_path - lappend ::auto_path [file join $ZipMountPoint auto0 auto1] [file join $ZipMountPoint auto0 auto2] -} -body { - # Try to load the commands. - set code3 [catch report1 msg3] - set code4 [catch report2 msg4] - list $code3 $msg3 $code4 $msg4 -} -cleanup { - catch {rename report1 {}} - catch {rename report2 {}} - set ::auto_path $tmpAutoPath - auto_reset -} -match glob -result {0 ok1 0 ok2} -test safe-zipfs-5.2 {example tclIndex commands, negative test in parent interpreter; zipfs} -setup { - set tmpAutoPath $::auto_path - lappend ::auto_path [file join $ZipMountPoint auto0] -} -body { - # Try to load the commands. - set code3 [catch report1 msg3] - set code4 [catch report2 msg4] - list $code3 $msg3 $code4 $msg4 -} -cleanup { - catch {rename report1 {}} - catch {rename report2 {}} - set ::auto_path $tmpAutoPath - auto_reset -} -match glob -result {1 {invalid command name "report1"} 1 {invalid command name "report2"}} -test safe-zipfs-5.3 {example pkgIndex.tcl packages, test in parent interpreter, child directories; zipfs} -setup { - set tmpAutoPath $::auto_path - lappend ::auto_path [file join $ZipMountPoint auto0] -} -body { - # Try to load the packages and run a command from each one. - set code3 [catch {package require SafeTestPackage1} msg3] - set code4 [catch {package require SafeTestPackage2} msg4] - set code5 [catch HeresPackage1 msg5] - set code6 [catch HeresPackage2 msg6] - list $code3 $msg3 $code4 $msg4 $code5 $msg5 $code6 $msg6 -} -cleanup { - set ::auto_path $tmpAutoPath - catch {package forget SafeTestPackage1} - catch {package forget SafeTestPackage2} - catch {rename HeresPackage1 {}} - catch {rename HeresPackage2 {}} -} -match glob -result {0 1.2.3 0 2.3.4 0 OK1 0 OK2} -test safe-zipfs-5.4 {example pkgIndex.tcl packages, test in parent interpreter, main directories; zipfs} -setup { - set tmpAutoPath $::auto_path - lappend ::auto_path [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2] -} -body { - # Try to load the packages and run a command from each one. - set code3 [catch {package require SafeTestPackage1} msg3] - set code4 [catch {package require SafeTestPackage2} msg4] - set code5 [catch HeresPackage1 msg5] - set code6 [catch HeresPackage2 msg6] - list $code3 $msg3 $code4 $msg4 $code5 $msg5 $code6 $msg6 -} -cleanup { - set ::auto_path $tmpAutoPath - catch {package forget SafeTestPackage1} - catch {package forget SafeTestPackage2} - catch {rename HeresPackage1 {}} - catch {rename HeresPackage2 {}} -} -match glob -result {0 1.2.3 0 2.3.4 0 OK1 0 OK2} -test safe-zipfs-5.5 {example modules packages, test in parent interpreter, replace path; zipfs} -setup { - set oldTm [tcl::tm::path list] - foreach path $oldTm { - tcl::tm::path remove $path + set PathMapp {} + lappend PathMapp $tcl_library TCLLIB $TestsDir TESTSDIR $ZipMountPoint ZIPDIR + + proc mapList {map listIn} { + set listOut {} + foreach element $listIn { + lappend listOut [string map $map $element] + } + return $listOut } - tcl::tm::path add [file join $ZipMountPoint auto0 modules] -} -body { - # Try to load the modules and run a command from each one. - set code0 [catch {package require test0} msg0] - set code1 [catch {package require mod1::test1} msg1] - set code2 [catch {package require mod2::test2} msg2] - set out0 [test0::try0] - set out1 [mod1::test1::try1] - set out2 [mod2::test2::try2] - list $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $out0 $out1 $out2 -} -cleanup { - tcl::tm::path remove [file join $ZipMountPoint auto0 modules] - foreach path [lreverse $oldTm] { - tcl::tm::path add $path + proc mapAndSortList {map listIn} { + set listOut {} + foreach element $listIn { + lappend listOut [string map $map $element] + } + lsort $listOut } - catch {package forget test0} - catch {package forget mod1::test1} - catch {package forget mod2::test2} - catch {namespace delete ::test0} - catch {namespace delete ::mod1} -} -match glob -result {0 0.5 0 1.0 0 2.0 -- res0 res1 res2} -test safe-zipfs-5.6 {example modules packages, test in parent interpreter, append to path; zipfs} -setup { - tcl::tm::path add [file join $ZipMountPoint auto0 modules] -} -body { - # Try to load the modules and run a command from each one. - set code0 [catch {package require test0} msg0] - set code1 [catch {package require mod1::test1} msg1] - set code2 [catch {package require mod2::test2} msg2] - set out0 [test0::try0] - set out1 [mod1::test1::try1] - set out2 [mod2::test2::try2] - list $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $out0 $out1 $out2 -} -cleanup { - tcl::tm::path remove [file join $ZipMountPoint auto0 modules] - catch {package forget test0} - catch {package forget mod1::test1} - catch {package forget mod2::test2} - catch {namespace delete ::test0} - catch {namespace delete ::mod1} -} -match glob -result {0 0.5 0 1.0 0 2.0 -- res0 res1 res2} - -# high level general test -# Use zipped example packages not http1.0 etc -test safe-zipfs-7.1 {tests that everything works at high level; zipfs} -setup { - set tmpAutoPath $::auto_path - lappend ::auto_path [file join $ZipMountPoint auto0] - set i [safe::interpCreate] - set ::auto_path $tmpAutoPath -} -body { - # no error shall occur: - # (because the default access_path shall include 1st level sub dirs so - # package require in a child works like in the parent) - set v [interp eval $i {package require SafeTestPackage1}] - # no error shall occur: - interp eval $i {HeresPackage1} - set v -} -cleanup { - safe::interpDelete $i -} -match glob -result 1.2.3 -test safe-zipfs-7.2 {tests specific path and interpFind/AddToAccessPath; zipfs} -setup { -} -body { - set i [safe::interpCreate -nostat -nested 1 -accessPath [list [info library]]] - # should not add anything (p0) - set token1 [safe::interpAddToAccessPath $i [info library]] - # should add as p* (not p1 if parent has a module path) - set token2 [safe::interpAddToAccessPath $i "/dummy/unixlike/test/path"] - # should add as p* (not p2 if parent has a module path) - set token3 [safe::interpAddToAccessPath $i [file join $ZipMountPoint auto0]] - set confA [safe::interpConfigure $i] - set mappA [mapList $PathMapp [dict get $confA -accessPath]] - # an error shall occur (SafeTestPackage1 is not anymore in the secure 0-level - # provided deep path) - list $token1 $token2 $token3 -- \ + + # Force actual loading of the safe package because we use un-exported (and + # thus un-autoindexed) APIs in this test result arguments: + catch {safe::interpConfigure} + + # Tests 5.* test the example files before using them to test safe interpreters. + + test safe-zipfs-5.1 {example tclIndex commands, test in parent interpreter; zipfs} -setup { + set tmpAutoPath $::auto_path + lappend ::auto_path [file join $ZipMountPoint auto0 auto1] [file join $ZipMountPoint auto0 auto2] + } -body { + # Try to load the commands. + set code3 [catch report1 msg3] + set code4 [catch report2 msg4] + list $code3 $msg3 $code4 $msg4 + } -cleanup { + catch {rename report1 {}} + catch {rename report2 {}} + set ::auto_path $tmpAutoPath + auto_reset + } -match glob -result {0 ok1 0 ok2} + test safe-zipfs-5.2 {example tclIndex commands, negative test in parent interpreter; zipfs} -setup { + set tmpAutoPath $::auto_path + lappend ::auto_path [file join $ZipMountPoint auto0] + } -body { + # Try to load the commands. + set code3 [catch report1 msg3] + set code4 [catch report2 msg4] + list $code3 $msg3 $code4 $msg4 + } -cleanup { + catch {rename report1 {}} + catch {rename report2 {}} + set ::auto_path $tmpAutoPath + auto_reset + } -match glob -result {1 {invalid command name "report1"} 1 {invalid command name "report2"}} + test safe-zipfs-5.3 {example pkgIndex.tcl packages, test in parent interpreter, child directories; zipfs} -setup { + set tmpAutoPath $::auto_path + lappend ::auto_path [file join $ZipMountPoint auto0] + } -body { + # Try to load the packages and run a command from each one. + set code3 [catch {package require SafeTestPackage1} msg3] + set code4 [catch {package require SafeTestPackage2} msg4] + set code5 [catch HeresPackage1 msg5] + set code6 [catch HeresPackage2 msg6] + list $code3 $msg3 $code4 $msg4 $code5 $msg5 $code6 $msg6 + } -cleanup { + set ::auto_path $tmpAutoPath + catch {package forget SafeTestPackage1} + catch {package forget SafeTestPackage2} + catch {rename HeresPackage1 {}} + catch {rename HeresPackage2 {}} + } -match glob -result {0 1.2.3 0 2.3.4 0 OK1 0 OK2} + test safe-zipfs-5.4 {example pkgIndex.tcl packages, test in parent interpreter, main directories; zipfs} -setup { + set tmpAutoPath $::auto_path + lappend ::auto_path [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2] + } -body { + # Try to load the packages and run a command from each one. + set code3 [catch {package require SafeTestPackage1} msg3] + set code4 [catch {package require SafeTestPackage2} msg4] + set code5 [catch HeresPackage1 msg5] + set code6 [catch HeresPackage2 msg6] + list $code3 $msg3 $code4 $msg4 $code5 $msg5 $code6 $msg6 + } -cleanup { + set ::auto_path $tmpAutoPath + catch {package forget SafeTestPackage1} + catch {package forget SafeTestPackage2} + catch {rename HeresPackage1 {}} + catch {rename HeresPackage2 {}} + } -match glob -result {0 1.2.3 0 2.3.4 0 OK1 0 OK2} + test safe-zipfs-5.5 {example modules packages, test in parent interpreter, replace path; zipfs} -setup { + set oldTm [tcl::tm::path list] + foreach path $oldTm { + tcl::tm::path remove $path + } + tcl::tm::path add [file join $ZipMountPoint auto0 modules] + } -body { + # Try to load the modules and run a command from each one. + set code0 [catch {package require test0} msg0] + set code1 [catch {package require mod1::test1} msg1] + set code2 [catch {package require mod2::test2} msg2] + set out0 [test0::try0] + set out1 [mod1::test1::try1] + set out2 [mod2::test2::try2] + list $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $out0 $out1 $out2 + } -cleanup { + tcl::tm::path remove [file join $ZipMountPoint auto0 modules] + foreach path [lreverse $oldTm] { + tcl::tm::path add $path + } + catch {package forget test0} + catch {package forget mod1::test1} + catch {package forget mod2::test2} + catch {namespace delete ::test0} + catch {namespace delete ::mod1} + } -match glob -result {0 0.5 0 1.0 0 2.0 -- res0 res1 res2} + test safe-zipfs-5.6 {example modules packages, test in parent interpreter, append to path; zipfs} -setup { + tcl::tm::path add [file join $ZipMountPoint auto0 modules] + } -body { + # Try to load the modules and run a command from each one. + set code0 [catch {package require test0} msg0] + set code1 [catch {package require mod1::test1} msg1] + set code2 [catch {package require mod2::test2} msg2] + set out0 [test0::try0] + set out1 [mod1::test1::try1] + set out2 [mod2::test2::try2] + list $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $out0 $out1 $out2 + } -cleanup { + tcl::tm::path remove [file join $ZipMountPoint auto0 modules] + catch {package forget test0} + catch {package forget mod1::test1} + catch {package forget mod2::test2} + catch {namespace delete ::test0} + catch {namespace delete ::mod1} + } -match glob -result {0 0.5 0 1.0 0 2.0 -- res0 res1 res2} + + # high level general test + # Use zipped example packages not http1.0 etc + test safe-zipfs-7.1 {tests that everything works at high level; zipfs} -setup { + set tmpAutoPath $::auto_path + lappend ::auto_path [file join $ZipMountPoint auto0] + set i [safe::interpCreate] + set ::auto_path $tmpAutoPath + } -body { + # no error shall occur: + # (because the default access_path shall include 1st level sub dirs so + # package require in a child works like in the parent) + set v [interp eval $i {package require SafeTestPackage1}] + # no error shall occur: + interp eval $i {HeresPackage1} + set v + } -cleanup { + safe::interpDelete $i + } -match glob -result 1.2.3 + test safe-zipfs-7.2 {tests specific path and interpFind/AddToAccessPath; zipfs} -setup { + } -body { + set i [safe::interpCreate -nostat -nested 1 -accessPath [list [info library]]] + # should not add anything (p0) + set token1 [safe::interpAddToAccessPath $i [info library]] + # should add as p* (not p1 if parent has a module path) + set token2 [safe::interpAddToAccessPath $i "/dummy/unixlike/test/path"] + # should add as p* (not p2 if parent has a module path) + set token3 [safe::interpAddToAccessPath $i [file join $ZipMountPoint auto0]] + set confA [safe::interpConfigure $i] + set mappA [mapList $PathMapp [dict get $confA -accessPath]] + # an error shall occur (SafeTestPackage1 is not anymore in the secure 0-level + # provided deep path) + list $token1 $token2 $token3 -- \ + [catch {interp eval $i {package require SafeTestPackage1}} msg] $msg -- \ + $mappA -- [safe::interpDelete $i] + } -cleanup { + } -match glob -result {{$p(:0:)} {$p(:*:)} {$p(:*:)} --\ + 1 {can't find package SafeTestPackage1} --\ + {TCLLIB */dummy/unixlike/test/path ZIPDIR/auto0} -- {}} + test safe-zipfs-7.4 {tests specific path and positive search; zipfs} -setup { + } -body { + set i [safe::interpCreate -nostat -nested 1 -accessPath [list [info library]]] + # should not add anything (p0) + set token1 [safe::interpAddToAccessPath $i [info library]] + # should add as p* (not p1 if parent has a module path) + set token2 [safe::interpAddToAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set confA [safe::interpConfigure $i] + set mappA [mapList $PathMapp [dict get $confA -accessPath]] + # this time, unlike test safe-zipfs-7.2, SafeTestPackage1 should be found + list $token1 $token2 -- \ [catch {interp eval $i {package require SafeTestPackage1}} msg] $msg -- \ $mappA -- [safe::interpDelete $i] -} -cleanup { -} -match glob -result {{$p(:0:)} {$p(:*:)} {$p(:*:)} --\ - 1 {can't find package SafeTestPackage1} --\ - {TCLLIB */dummy/unixlike/test/path ZIPDIR/auto0} -- {}} -test safe-zipfs-7.4 {tests specific path and positive search; zipfs} -setup { -} -body { - set i [safe::interpCreate -nostat -nested 1 -accessPath [list [info library]]] - # should not add anything (p0) - set token1 [safe::interpAddToAccessPath $i [info library]] - # should add as p* (not p1 if parent has a module path) - set token2 [safe::interpAddToAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set confA [safe::interpConfigure $i] - set mappA [mapList $PathMapp [dict get $confA -accessPath]] - # this time, unlike test safe-zipfs-7.2, SafeTestPackage1 should be found - list $token1 $token2 -- \ - [catch {interp eval $i {package require SafeTestPackage1}} msg] $msg -- \ - $mappA -- [safe::interpDelete $i] - # Note that the glob match elides directories (those from the module path) - # other than the first and last in the access path. -} -cleanup { -} -match glob -result {{$p(:0:)} {$p(:*:)} -- 0 1.2.3 --\ - {TCLLIB * ZIPDIR/auto0/auto1} -- {}} - -test safe-zipfs-9.9 {interpConfigure change the access path; tclIndex commands unaffected by token rearrangement (dummy test of doreset); zipfs} -setup { -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]]] - # Inspect. - set confA [safe::interpConfigure $i] - set mappA [mapList $PathMapp [dict get $confA -accessPath]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Load auto_load data. - interp eval $i {catch nonExistentCommand} - - # Load and run the commands. - # This guarantees the test will pass even if the tokens are swapped. - set code1 [catch {interp eval $i {report1}} msg1] - set code2 [catch {interp eval $i {report2}} msg2] - - # Rearrange access path. Swap tokens {$p(:1:)} and {$p(:2:)}. - safe::interpConfigure $i -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto2] \ - [file join $ZipMountPoint auto0 auto1]] - # Inspect. - set confB [safe::interpConfigure $i] - set mappB [mapList $PathMapp [dict get $confB -accessPath]] - set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Run the commands. - set code3 [catch {interp eval $i {report1}} msg3] - set code4 [catch {interp eval $i {report2}} msg4] - - list $path1 $path2 -- $path3 $path4 -- $code3 $msg3 $code4 $msg4 -- $mappA -- $mappB -} -cleanup { - safe::interpDelete $i -} -match glob -result {{$p(:1:)} {$p(:2:)} -- {$p(:2:)} {$p(:1:)} -- 0 ok1 0 ok2 --\ - {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} --\ - {TCLLIB ZIPDIR/auto0/auto2 ZIPDIR/auto0/auto1*}} -test safe-zipfs-9.10 {interpConfigure change the access path; tclIndex commands unaffected by token rearrangement (actual test of doreset); zipfs} -setup { -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]]] - # Inspect. - set confA [safe::interpConfigure $i] - set mappA [mapList $PathMapp [dict get $confA -accessPath]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Load auto_load data. - interp eval $i {catch nonExistentCommand} - - # Do not load the commands. With the tokens swapped, the test - # will pass only if the Safe Base has called auto_reset. - - # Rearrange access path. Swap tokens {$p(:1:)} and {$p(:2:)}. - safe::interpConfigure $i -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto2] \ - [file join $ZipMountPoint auto0 auto1]] - # Inspect. - set confB [safe::interpConfigure $i] - set mappB [mapList $PathMapp [dict get $confB -accessPath]] - set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Load and run the commands. - set code3 [catch {interp eval $i {report1}} msg3] - set code4 [catch {interp eval $i {report2}} msg4] - - list $path1 $path2 -- $path3 $path4 -- $code3 $msg3 $code4 $msg4 -- $mappA -- $mappB -} -cleanup { - safe::interpDelete $i -} -match glob -result {{$p(:1:)} {$p(:2:)} -- {$p(:2:)} {$p(:1:)} --\ - 0 ok1 0 ok2 --\ - {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} --\ - {TCLLIB ZIPDIR/auto0/auto2 ZIPDIR/auto0/auto1*}} -test safe-zipfs-9.11 {interpConfigure change the access path; pkgIndex.tcl packages unaffected by token rearrangement; zipfs} -setup { -} -body { - # For complete correspondence to safe-stock87-9.11, include auto0 in access path. - set i [safe::interpCreate -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0] \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]]] - # Inspect. - set confA [safe::interpConfigure $i] - set mappA [mapList $PathMapp [dict get $confA -accessPath]] - set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Load pkgIndex.tcl data. - catch {interp eval $i {package require NOEXIST}} - - # Rearrange access path. Swap tokens {$p(:2:)} and {$p(:3:)}. - # This would have no effect because the records in Pkg of these directories - # were from access as children of {$p(:1:)}. - safe::interpConfigure $i -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0] \ - [file join $ZipMountPoint auto0 auto2] \ - [file join $ZipMountPoint auto0 auto1]] - # Inspect. - set confB [safe::interpConfigure $i] - set mappB [mapList $PathMapp [dict get $confB -accessPath]] - set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Try to load the packages and run a command from each one. - set code3 [catch {interp eval $i {package require SafeTestPackage1}} msg3 opts3] - set code4 [catch {interp eval $i {package require SafeTestPackage2}} msg4 opts4] - set code5 [catch {interp eval $i {HeresPackage1}} msg5 opts5] - set code6 [catch {interp eval $i {HeresPackage2}} msg6 opts6] - - list $path1 $path2 -- $path3 $path4 -- $code3 $msg3 $code4 $msg4 -- \ - $mappA -- $mappB -- $code5 $msg5 $code6 $msg6 -} -cleanup { - safe::interpDelete $i -} -match glob -result {{$p(:2:)} {$p(:3:)} -- {$p(:3:)} {$p(:2:)} -- 0 1.2.3 0 2.3.4 --\ - {TCLLIB ZIPDIR/auto0 ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} --\ - {TCLLIB ZIPDIR/auto0 ZIPDIR/auto0/auto2 ZIPDIR/auto0/auto1*} --\ - 0 OK1 0 OK2} -test safe-zipfs-9.12 {interpConfigure change the access path; pkgIndex.tcl packages unaffected by token rearrangement, 9.10 without path auto0; zipfs} -setup { -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]]] - # Inspect. - set confA [safe::interpConfigure $i] - set mappA [mapList $PathMapp [dict get $confA -accessPath]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Load pkgIndex.tcl data. - catch {interp eval $i {package require NOEXIST}} - - # Rearrange access path. Swap tokens {$p(:1:)} and {$p(:2:)}. - safe::interpConfigure $i -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto2] \ - [file join $ZipMountPoint auto0 auto1]] - # Inspect. - set confB [safe::interpConfigure $i] - set mappB [mapList $PathMapp [dict get $confB -accessPath]] - set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Try to load the packages and run a command from each one. - set code3 [catch {interp eval $i {package require SafeTestPackage1}} msg3 opts3] - set code4 [catch {interp eval $i {package require SafeTestPackage2}} msg4 opts4] - set code5 [catch {interp eval $i {HeresPackage1}} msg5 opts5] - set code6 [catch {interp eval $i {HeresPackage2}} msg6 opts6] - - list $path1 $path2 -- $path3 $path4 -- $code3 $msg3 $code4 $msg4 -- \ - $mappA -- $mappB -- $code5 $msg5 $code6 $msg6 -} -cleanup { - safe::interpDelete $i -} -match glob -result {{$p(:1:)} {$p(:2:)} -- {$p(:2:)} {$p(:1:)} --\ - 0 1.2.3 0 2.3.4 --\ - {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} --\ - {TCLLIB ZIPDIR/auto0/auto2 ZIPDIR/auto0/auto1*} --\ - 0 OK1 0 OK2} -test safe-zipfs-9.13 {interpConfigure change the access path; pkgIndex.tcl packages fail if directory de-listed; zipfs} -setup { -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]]] - # Inspect. - set confA [safe::interpConfigure $i] - set mappA [mapList $PathMapp [dict get $confA -accessPath]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] - - # Load pkgIndex.tcl data. - catch {interp eval $i {package require NOEXIST}} - - # Limit access path. Remove tokens {$p(:1:)} and {$p(:2:)}. - safe::interpConfigure $i -accessPath [list $tcl_library] - - # Inspect. - set confB [safe::interpConfigure $i] - set mappB [mapList $PathMapp [dict get $confB -accessPath]] - set code4 [catch {::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]} path4] - set code5 [catch {::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]} path5] - - # Try to load the packages. - set code3 [catch {interp eval $i {package require SafeTestPackage1}} msg3] - set code6 [catch {interp eval $i {package require SafeTestPackage2}} msg6] - - list $path1 $path2 -- $code4 $path4 -- $code5 $path5 -- $code3 $code6 -- \ - $mappA -- $mappB -} -cleanup { - safe::interpDelete $i -} -match glob -result {{$p(:1:)} {$p(:2:)} -- 1 {* not found in access path} --\ - 1 {* not found in access path} -- 1 1 --\ - {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} -- {TCLLIB*}} -test safe-zipfs-9.20 {check module loading; zipfs} -setup { - set oldTm [tcl::tm::path list] - foreach path $oldTm { - tcl::tm::path remove $path - } - tcl::tm::path add [file join $ZipMountPoint auto0 modules] -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library]] - - # Inspect. - set confA [safe::interpConfigure $i] - set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] - set modsA [interp eval $i {tcl::tm::path list}] - set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Try to load the packages and run a command from each one. - set code0 [catch {interp eval $i {package require test0}} msg0] - set code1 [catch {interp eval $i {package require mod1::test1}} msg1] - set code2 [catch {interp eval $i {package require mod2::test2}} msg2] - set out0 [interp eval $i {test0::try0}] - set out1 [interp eval $i {mod1::test1::try1}] - set out2 [interp eval $i {mod2::test2::try2}] - - list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ - $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $out0 $out1 $out2 -} -cleanup { - tcl::tm::path remove [file join $ZipMountPoint auto0 modules] - foreach path [lreverse $oldTm] { - tcl::tm::path add $path - } - safe::interpDelete $i -} -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ - 0 0.5 0 1.0 0 2.0 --\ - {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ - ZIPDIR/auto0/modules/mod2} -- res0 res1 res2} -# - The command safe::InterpSetConfig adds the parent's [tcl::tm::list] in -# tokenized form to the child's access path, and then adds all the -# descendants, discovered recursively by using glob. -# - The order of the directories in the list returned by glob is system-dependent, -# and therefore this is true also for (a) the order of token assignment to -# descendants of the [tcl::tm::list] roots; and (b) the order of those same -# directories in the access path. Both those things must be sorted before -# comparing with expected results. The test is therefore not totally strict, -# but will notice missing or surplus directories. -test safe-zipfs-9.21 {interpConfigure change the access path; check module loading; stale data case 1; zipfs} -setup { - set oldTm [tcl::tm::path list] - foreach path $oldTm { - tcl::tm::path remove $path - } - tcl::tm::path add [file join $ZipMountPoint auto0 modules] -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library]] - - # Inspect. - set confA [safe::interpConfigure $i] - set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] - set modsA [interp eval $i {tcl::tm::path list}] - set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Add to access path. - # This injects more tokens, pushing modules to higher token numbers. - safe::interpConfigure $i -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]] - # Inspect. - set confB [safe::interpConfigure $i] - set sortB [mapAndSortList $PathMapp [dict get $confB -accessPath]] - set modsB [interp eval $i {tcl::tm::path list}] - set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path5 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Load pkg data. - catch {interp eval $i {package require NOEXIST}} - catch {interp eval $i {package require mod1::NOEXIST}} - catch {interp eval $i {package require mod2::NOEXIST}} - - # Try to load the packages and run a command from each one. - set code0 [catch {interp eval $i {package require test0}} msg0] - set code1 [catch {interp eval $i {package require mod1::test1}} msg1] - set code2 [catch {interp eval $i {package require mod2::test2}} msg2] - set out0 [interp eval $i {test0::try0}] - set out1 [interp eval $i {mod1::test1::try1}] - set out2 [interp eval $i {mod2::test2::try2}] - - list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ - [lsort [list $path3 $path4 $path5]] -- $modsB -- \ - $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $sortB -- \ - $out0 $out1 $out2 -} -cleanup { - tcl::tm::path remove [file join $ZipMountPoint auto0 modules] - foreach path [lreverse $oldTm] { - tcl::tm::path add $path - } - safe::interpDelete $i -} -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ - {{$p(:3:)} {$p(:4:)} {$p(:5:)}} -- {{$p(:3:)}} --\ - 0 0.5 0 1.0 0 2.0 --\ - {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ - ZIPDIR/auto0/modules/mod2} --\ - {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2 ZIPDIR/auto0/modules\ - ZIPDIR/auto0/modules/mod1 ZIPDIR/auto0/modules/mod2} --\ - res0 res1 res2} -# See comments on lsort after test safe-zipfs-9.20. -test safe-zipfs-9.22 {interpConfigure change the access path; check module loading; stale data case 0; zipfs} -setup { - set oldTm [tcl::tm::path list] - foreach path $oldTm { - tcl::tm::path remove $path - } - tcl::tm::path add [file join $ZipMountPoint auto0 modules] -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library]] - - # Inspect. - set confA [safe::interpConfigure $i] - set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] - set modsA [interp eval $i {tcl::tm::path list}] - set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Add to access path. - # This injects more tokens, pushing modules to higher token numbers. - safe::interpConfigure $i -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]] - # Inspect. - set confB [safe::interpConfigure $i] - set sortB [mapAndSortList $PathMapp [dict get $confB -accessPath]] - set modsB [interp eval $i {tcl::tm::path list}] - set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path5 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Try to load the packages and run a command from each one. - set code0 [catch {interp eval $i {package require test0}} msg0] - set code1 [catch {interp eval $i {package require mod1::test1}} msg1] - set code2 [catch {interp eval $i {package require mod2::test2}} msg2] - set out0 [interp eval $i {test0::try0}] - set out1 [interp eval $i {mod1::test1::try1}] - set out2 [interp eval $i {mod2::test2::try2}] - - list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ - [lsort [list $path3 $path4 $path5]] -- $modsB -- \ - $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $sortB -- \ - $out0 $out1 $out2 -} -cleanup { - tcl::tm::path remove [file join $ZipMountPoint auto0 modules] - foreach path [lreverse $oldTm] { - tcl::tm::path add $path - } - safe::interpDelete $i -} -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ - {{$p(:3:)} {$p(:4:)} {$p(:5:)}} -- {{$p(:3:)}} --\ - 0 0.5 0 1.0 0 2.0 --\ - {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ - ZIPDIR/auto0/modules/mod2} --\ - {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2 ZIPDIR/auto0/modules\ - ZIPDIR/auto0/modules/mod1 ZIPDIR/auto0/modules/mod2} --\ - res0 res1 res2} -# See comments on lsort after test safe-zipfs-9.20. -test safe-zipfs-9.23 {interpConfigure change the access path; check module loading; stale data case 3; zipfs} -setup { - set oldTm [tcl::tm::path list] - foreach path $oldTm { - tcl::tm::path remove $path - } - tcl::tm::path add [file join $ZipMountPoint auto0 modules] -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library]] - - # Inspect. - set confA [safe::interpConfigure $i] - set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] - set modsA [interp eval $i {tcl::tm::path list}] - set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Force the interpreter to acquire pkg data which will soon become stale. - catch {interp eval $i {package require NOEXIST}} - catch {interp eval $i {package require mod1::NOEXIST}} - catch {interp eval $i {package require mod2::NOEXIST}} - - # Add to access path. - # This injects more tokens, pushing modules to higher token numbers. - safe::interpConfigure $i -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]] - # Inspect. - set confB [safe::interpConfigure $i] - set sortB [mapAndSortList $PathMapp [dict get $confB -accessPath]] - set modsB [interp eval $i {tcl::tm::path list}] - set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path5 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Refresh stale pkg data. - catch {interp eval $i {package require NOEXIST}} - catch {interp eval $i {package require mod1::NOEXIST}} - catch {interp eval $i {package require mod2::NOEXIST}} - - # Try to load the packages and run a command from each one. - set code0 [catch {interp eval $i {package require test0}} msg0] - set code1 [catch {interp eval $i {package require mod1::test1}} msg1] - set code2 [catch {interp eval $i {package require mod2::test2}} msg2] - set out0 [interp eval $i {test0::try0}] - set out1 [interp eval $i {mod1::test1::try1}] - set out2 [interp eval $i {mod2::test2::try2}] - - list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ - [lsort [list $path3 $path4 $path5]] -- $modsB -- \ - $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $sortB -- \ - $out0 $out1 $out2 -} -cleanup { - tcl::tm::path remove [file join $ZipMountPoint auto0 modules] - foreach path [lreverse $oldTm] { - tcl::tm::path add $path - } - safe::interpDelete $i -} -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ - {{$p(:3:)} {$p(:4:)} {$p(:5:)}} -- {{$p(:3:)}} --\ - 0 0.5 0 1.0 0 2.0 --\ - {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ - ZIPDIR/auto0/modules/mod2} --\ - {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2 ZIPDIR/auto0/modules\ - ZIPDIR/auto0/modules/mod1 ZIPDIR/auto0/modules/mod2} --\ - res0 res1 res2} -# See comments on lsort after test safe-zipfs-9.20. -test safe-zipfs-9.24 {interpConfigure change the access path; check module loading; stale data case 2 (worst case); zipfs} -setup { - set oldTm [tcl::tm::path list] - foreach path $oldTm { - tcl::tm::path remove $path - } - tcl::tm::path add [file join $ZipMountPoint auto0 modules] -} -body { - set i [safe::interpCreate -accessPath [list $tcl_library]] - - # Inspect. - set confA [safe::interpConfigure $i] - set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] - set modsA [interp eval $i {tcl::tm::path list}] - set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Force the interpreter to acquire pkg data which will soon become stale. - catch {interp eval $i {package require NOEXIST}} - catch {interp eval $i {package require mod1::NOEXIST}} - catch {interp eval $i {package require mod2::NOEXIST}} - - # Add to access path. - # This injects more tokens, pushing modules to higher token numbers. - safe::interpConfigure $i -accessPath [list $tcl_library \ - [file join $ZipMountPoint auto0 auto1] \ - [file join $ZipMountPoint auto0 auto2]] - # Inspect. - set confB [safe::interpConfigure $i] - set sortB [mapAndSortList $PathMapp [dict get $confB -accessPath]] - set modsB [interp eval $i {tcl::tm::path list}] - set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] - set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] - set path5 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] - - # Try to load the packages and run a command from each one. - set code0 [catch {interp eval $i {package require test0}} msg0] - set code1 [catch {interp eval $i {package require mod1::test1}} msg1] - set code2 [catch {interp eval $i {package require mod2::test2}} msg2] - set out0 [interp eval $i {test0::try0}] - set out1 [interp eval $i {mod1::test1::try1}] - set out2 [interp eval $i {mod2::test2::try2}] - - list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ - [lsort [list $path3 $path4 $path5]] -- $modsB -- \ - $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $sortB -- \ - $out0 $out1 $out2 -} -cleanup { - tcl::tm::path remove [file join $ZipMountPoint auto0 modules] - foreach path [lreverse $oldTm] { - tcl::tm::path add $path - } - safe::interpDelete $i -} -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ - {{$p(:3:)} {$p(:4:)} {$p(:5:)}} -- {{$p(:3:)}} --\ - 0 0.5 0 1.0 0 2.0 --\ - {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ - ZIPDIR/auto0/modules/mod2} --\ - {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2 ZIPDIR/auto0/modules\ - ZIPDIR/auto0/modules/mod1 ZIPDIR/auto0/modules/mod2} --\ - res0 res1 res2} -# See comments on lsort after test safe-zipfs-9.20. - -# cleanup -set ::auto_path $SaveAutoPath -zipfs unmount ${ZipMountPoint} -unset SaveAutoPath TestsDir ZipMountPoint PathMapp -rename mapList {} -rename mapAndSortList {} -::tcltest::cleanupTests -return + # Note that the glob match elides directories (those from the module path) + # other than the first and last in the access path. + } -cleanup { + } -match glob -result {{$p(:0:)} {$p(:*:)} -- 0 1.2.3 --\ + {TCLLIB * ZIPDIR/auto0/auto1} -- {}} + + test safe-zipfs-9.9 {interpConfigure change the access path; tclIndex commands unaffected by token rearrangement (dummy test of doreset); zipfs} -setup { + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]]] + # Inspect. + set confA [safe::interpConfigure $i] + set mappA [mapList $PathMapp [dict get $confA -accessPath]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Load auto_load data. + interp eval $i {catch nonExistentCommand} + + # Load and run the commands. + # This guarantees the test will pass even if the tokens are swapped. + set code1 [catch {interp eval $i {report1}} msg1] + set code2 [catch {interp eval $i {report2}} msg2] + + # Rearrange access path. Swap tokens {$p(:1:)} and {$p(:2:)}. + safe::interpConfigure $i -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto2] \ + [file join $ZipMountPoint auto0 auto1]] + # Inspect. + set confB [safe::interpConfigure $i] + set mappB [mapList $PathMapp [dict get $confB -accessPath]] + set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Run the commands. + set code3 [catch {interp eval $i {report1}} msg3] + set code4 [catch {interp eval $i {report2}} msg4] + + list $path1 $path2 -- $path3 $path4 -- $code3 $msg3 $code4 $msg4 -- $mappA -- $mappB + } -cleanup { + safe::interpDelete $i + } -match glob -result {{$p(:1:)} {$p(:2:)} -- {$p(:2:)} {$p(:1:)} -- 0 ok1 0 ok2 --\ + {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} --\ + {TCLLIB ZIPDIR/auto0/auto2 ZIPDIR/auto0/auto1*}} + test safe-zipfs-9.10 {interpConfigure change the access path; tclIndex commands unaffected by token rearrangement (actual test of doreset); zipfs} -setup { + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]]] + # Inspect. + set confA [safe::interpConfigure $i] + set mappA [mapList $PathMapp [dict get $confA -accessPath]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Load auto_load data. + interp eval $i {catch nonExistentCommand} + + # Do not load the commands. With the tokens swapped, the test + # will pass only if the Safe Base has called auto_reset. + + # Rearrange access path. Swap tokens {$p(:1:)} and {$p(:2:)}. + safe::interpConfigure $i -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto2] \ + [file join $ZipMountPoint auto0 auto1]] + # Inspect. + set confB [safe::interpConfigure $i] + set mappB [mapList $PathMapp [dict get $confB -accessPath]] + set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Load and run the commands. + set code3 [catch {interp eval $i {report1}} msg3] + set code4 [catch {interp eval $i {report2}} msg4] + + list $path1 $path2 -- $path3 $path4 -- $code3 $msg3 $code4 $msg4 -- $mappA -- $mappB + } -cleanup { + safe::interpDelete $i + } -match glob -result {{$p(:1:)} {$p(:2:)} -- {$p(:2:)} {$p(:1:)} --\ + 0 ok1 0 ok2 --\ + {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} --\ + {TCLLIB ZIPDIR/auto0/auto2 ZIPDIR/auto0/auto1*}} + test safe-zipfs-9.11 {interpConfigure change the access path; pkgIndex.tcl packages unaffected by token rearrangement; zipfs} -setup { + } -body { + # For complete correspondence to safe-stock87-9.11, include auto0 in access path. + set i [safe::interpCreate -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0] \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]]] + # Inspect. + set confA [safe::interpConfigure $i] + set mappA [mapList $PathMapp [dict get $confA -accessPath]] + set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Load pkgIndex.tcl data. + catch {interp eval $i {package require NOEXIST}} + + # Rearrange access path. Swap tokens {$p(:2:)} and {$p(:3:)}. + # This would have no effect because the records in Pkg of these directories + # were from access as children of {$p(:1:)}. + safe::interpConfigure $i -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0] \ + [file join $ZipMountPoint auto0 auto2] \ + [file join $ZipMountPoint auto0 auto1]] + # Inspect. + set confB [safe::interpConfigure $i] + set mappB [mapList $PathMapp [dict get $confB -accessPath]] + set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Try to load the packages and run a command from each one. + set code3 [catch {interp eval $i {package require SafeTestPackage1}} msg3 opts3] + set code4 [catch {interp eval $i {package require SafeTestPackage2}} msg4 opts4] + set code5 [catch {interp eval $i {HeresPackage1}} msg5 opts5] + set code6 [catch {interp eval $i {HeresPackage2}} msg6 opts6] + + list $path1 $path2 -- $path3 $path4 -- $code3 $msg3 $code4 $msg4 -- \ + $mappA -- $mappB -- $code5 $msg5 $code6 $msg6 + } -cleanup { + safe::interpDelete $i + } -match glob -result {{$p(:2:)} {$p(:3:)} -- {$p(:3:)} {$p(:2:)} -- 0 1.2.3 0 2.3.4 --\ + {TCLLIB ZIPDIR/auto0 ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} --\ + {TCLLIB ZIPDIR/auto0 ZIPDIR/auto0/auto2 ZIPDIR/auto0/auto1*} --\ + 0 OK1 0 OK2} + test safe-zipfs-9.12 {interpConfigure change the access path; pkgIndex.tcl packages unaffected by token rearrangement, 9.10 without path auto0; zipfs} -setup { + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]]] + # Inspect. + set confA [safe::interpConfigure $i] + set mappA [mapList $PathMapp [dict get $confA -accessPath]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Load pkgIndex.tcl data. + catch {interp eval $i {package require NOEXIST}} + + # Rearrange access path. Swap tokens {$p(:1:)} and {$p(:2:)}. + safe::interpConfigure $i -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto2] \ + [file join $ZipMountPoint auto0 auto1]] + # Inspect. + set confB [safe::interpConfigure $i] + set mappB [mapList $PathMapp [dict get $confB -accessPath]] + set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Try to load the packages and run a command from each one. + set code3 [catch {interp eval $i {package require SafeTestPackage1}} msg3 opts3] + set code4 [catch {interp eval $i {package require SafeTestPackage2}} msg4 opts4] + set code5 [catch {interp eval $i {HeresPackage1}} msg5 opts5] + set code6 [catch {interp eval $i {HeresPackage2}} msg6 opts6] + + list $path1 $path2 -- $path3 $path4 -- $code3 $msg3 $code4 $msg4 -- \ + $mappA -- $mappB -- $code5 $msg5 $code6 $msg6 + } -cleanup { + safe::interpDelete $i + } -match glob -result {{$p(:1:)} {$p(:2:)} -- {$p(:2:)} {$p(:1:)} --\ + 0 1.2.3 0 2.3.4 --\ + {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} --\ + {TCLLIB ZIPDIR/auto0/auto2 ZIPDIR/auto0/auto1*} --\ + 0 OK1 0 OK2} + test safe-zipfs-9.13 {interpConfigure change the access path; pkgIndex.tcl packages fail if directory de-listed; zipfs} -setup { + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]]] + # Inspect. + set confA [safe::interpConfigure $i] + set mappA [mapList $PathMapp [dict get $confA -accessPath]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]] + + # Load pkgIndex.tcl data. + catch {interp eval $i {package require NOEXIST}} + + # Limit access path. Remove tokens {$p(:1:)} and {$p(:2:)}. + safe::interpConfigure $i -accessPath [list $tcl_library] + + # Inspect. + set confB [safe::interpConfigure $i] + set mappB [mapList $PathMapp [dict get $confB -accessPath]] + set code4 [catch {::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto1]} path4] + set code5 [catch {::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 auto2]} path5] + + # Try to load the packages. + set code3 [catch {interp eval $i {package require SafeTestPackage1}} msg3] + set code6 [catch {interp eval $i {package require SafeTestPackage2}} msg6] + + list $path1 $path2 -- $code4 $path4 -- $code5 $path5 -- $code3 $code6 -- \ + $mappA -- $mappB + } -cleanup { + safe::interpDelete $i + } -match glob -result {{$p(:1:)} {$p(:2:)} -- 1 {* not found in access path} --\ + 1 {* not found in access path} -- 1 1 --\ + {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2*} -- {TCLLIB*}} + test safe-zipfs-9.20 {check module loading; zipfs} -setup { + set oldTm [tcl::tm::path list] + foreach path $oldTm { + tcl::tm::path remove $path + } + tcl::tm::path add [file join $ZipMountPoint auto0 modules] + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library]] + + # Inspect. + set confA [safe::interpConfigure $i] + set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] + set modsA [interp eval $i {tcl::tm::path list}] + set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Try to load the packages and run a command from each one. + set code0 [catch {interp eval $i {package require test0}} msg0] + set code1 [catch {interp eval $i {package require mod1::test1}} msg1] + set code2 [catch {interp eval $i {package require mod2::test2}} msg2] + set out0 [interp eval $i {test0::try0}] + set out1 [interp eval $i {mod1::test1::try1}] + set out2 [interp eval $i {mod2::test2::try2}] + + list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ + $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $out0 $out1 $out2 + } -cleanup { + tcl::tm::path remove [file join $ZipMountPoint auto0 modules] + foreach path [lreverse $oldTm] { + tcl::tm::path add $path + } + safe::interpDelete $i + } -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ + 0 0.5 0 1.0 0 2.0 --\ + {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ + ZIPDIR/auto0/modules/mod2} -- res0 res1 res2} + # - The command safe::InterpSetConfig adds the parent's [tcl::tm::list] in + # tokenized form to the child's access path, and then adds all the + # descendants, discovered recursively by using glob. + # - The order of the directories in the list returned by glob is system-dependent, + # and therefore this is true also for (a) the order of token assignment to + # descendants of the [tcl::tm::list] roots; and (b) the order of those same + # directories in the access path. Both those things must be sorted before + # comparing with expected results. The test is therefore not totally strict, + # but will notice missing or surplus directories. + test safe-zipfs-9.21 {interpConfigure change the access path; check module loading; stale data case 1; zipfs} -setup { + set oldTm [tcl::tm::path list] + foreach path $oldTm { + tcl::tm::path remove $path + } + tcl::tm::path add [file join $ZipMountPoint auto0 modules] + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library]] + + # Inspect. + set confA [safe::interpConfigure $i] + set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] + set modsA [interp eval $i {tcl::tm::path list}] + set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Add to access path. + # This injects more tokens, pushing modules to higher token numbers. + safe::interpConfigure $i -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]] + # Inspect. + set confB [safe::interpConfigure $i] + set sortB [mapAndSortList $PathMapp [dict get $confB -accessPath]] + set modsB [interp eval $i {tcl::tm::path list}] + set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path5 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Load pkg data. + catch {interp eval $i {package require NOEXIST}} + catch {interp eval $i {package require mod1::NOEXIST}} + catch {interp eval $i {package require mod2::NOEXIST}} + + # Try to load the packages and run a command from each one. + set code0 [catch {interp eval $i {package require test0}} msg0] + set code1 [catch {interp eval $i {package require mod1::test1}} msg1] + set code2 [catch {interp eval $i {package require mod2::test2}} msg2] + set out0 [interp eval $i {test0::try0}] + set out1 [interp eval $i {mod1::test1::try1}] + set out2 [interp eval $i {mod2::test2::try2}] + + list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ + [lsort [list $path3 $path4 $path5]] -- $modsB -- \ + $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $sortB -- \ + $out0 $out1 $out2 + } -cleanup { + tcl::tm::path remove [file join $ZipMountPoint auto0 modules] + foreach path [lreverse $oldTm] { + tcl::tm::path add $path + } + safe::interpDelete $i + } -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ + {{$p(:3:)} {$p(:4:)} {$p(:5:)}} -- {{$p(:3:)}} --\ + 0 0.5 0 1.0 0 2.0 --\ + {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ + ZIPDIR/auto0/modules/mod2} --\ + {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2 ZIPDIR/auto0/modules\ + ZIPDIR/auto0/modules/mod1 ZIPDIR/auto0/modules/mod2} --\ + res0 res1 res2} + # See comments on lsort after test safe-zipfs-9.20. + test safe-zipfs-9.22 {interpConfigure change the access path; check module loading; stale data case 0; zipfs} -setup { + set oldTm [tcl::tm::path list] + foreach path $oldTm { + tcl::tm::path remove $path + } + tcl::tm::path add [file join $ZipMountPoint auto0 modules] + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library]] + + # Inspect. + set confA [safe::interpConfigure $i] + set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] + set modsA [interp eval $i {tcl::tm::path list}] + set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Add to access path. + # This injects more tokens, pushing modules to higher token numbers. + safe::interpConfigure $i -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]] + # Inspect. + set confB [safe::interpConfigure $i] + set sortB [mapAndSortList $PathMapp [dict get $confB -accessPath]] + set modsB [interp eval $i {tcl::tm::path list}] + set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path5 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Try to load the packages and run a command from each one. + set code0 [catch {interp eval $i {package require test0}} msg0] + set code1 [catch {interp eval $i {package require mod1::test1}} msg1] + set code2 [catch {interp eval $i {package require mod2::test2}} msg2] + set out0 [interp eval $i {test0::try0}] + set out1 [interp eval $i {mod1::test1::try1}] + set out2 [interp eval $i {mod2::test2::try2}] + + list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ + [lsort [list $path3 $path4 $path5]] -- $modsB -- \ + $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $sortB -- \ + $out0 $out1 $out2 + } -cleanup { + tcl::tm::path remove [file join $ZipMountPoint auto0 modules] + foreach path [lreverse $oldTm] { + tcl::tm::path add $path + } + safe::interpDelete $i + } -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ + {{$p(:3:)} {$p(:4:)} {$p(:5:)}} -- {{$p(:3:)}} --\ + 0 0.5 0 1.0 0 2.0 --\ + {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ + ZIPDIR/auto0/modules/mod2} --\ + {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2 ZIPDIR/auto0/modules\ + ZIPDIR/auto0/modules/mod1 ZIPDIR/auto0/modules/mod2} --\ + res0 res1 res2} + # See comments on lsort after test safe-zipfs-9.20. + test safe-zipfs-9.23 {interpConfigure change the access path; check module loading; stale data case 3; zipfs} -setup { + set oldTm [tcl::tm::path list] + foreach path $oldTm { + tcl::tm::path remove $path + } + tcl::tm::path add [file join $ZipMountPoint auto0 modules] + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library]] + + # Inspect. + set confA [safe::interpConfigure $i] + set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] + set modsA [interp eval $i {tcl::tm::path list}] + set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Force the interpreter to acquire pkg data which will soon become stale. + catch {interp eval $i {package require NOEXIST}} + catch {interp eval $i {package require mod1::NOEXIST}} + catch {interp eval $i {package require mod2::NOEXIST}} + + # Add to access path. + # This injects more tokens, pushing modules to higher token numbers. + safe::interpConfigure $i -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]] + # Inspect. + set confB [safe::interpConfigure $i] + set sortB [mapAndSortList $PathMapp [dict get $confB -accessPath]] + set modsB [interp eval $i {tcl::tm::path list}] + set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path5 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Refresh stale pkg data. + catch {interp eval $i {package require NOEXIST}} + catch {interp eval $i {package require mod1::NOEXIST}} + catch {interp eval $i {package require mod2::NOEXIST}} + + # Try to load the packages and run a command from each one. + set code0 [catch {interp eval $i {package require test0}} msg0] + set code1 [catch {interp eval $i {package require mod1::test1}} msg1] + set code2 [catch {interp eval $i {package require mod2::test2}} msg2] + set out0 [interp eval $i {test0::try0}] + set out1 [interp eval $i {mod1::test1::try1}] + set out2 [interp eval $i {mod2::test2::try2}] + + list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ + [lsort [list $path3 $path4 $path5]] -- $modsB -- \ + $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $sortB -- \ + $out0 $out1 $out2 + } -cleanup { + tcl::tm::path remove [file join $ZipMountPoint auto0 modules] + foreach path [lreverse $oldTm] { + tcl::tm::path add $path + } + safe::interpDelete $i + } -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ + {{$p(:3:)} {$p(:4:)} {$p(:5:)}} -- {{$p(:3:)}} --\ + 0 0.5 0 1.0 0 2.0 --\ + {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ + ZIPDIR/auto0/modules/mod2} --\ + {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2 ZIPDIR/auto0/modules\ + ZIPDIR/auto0/modules/mod1 ZIPDIR/auto0/modules/mod2} --\ + res0 res1 res2} + # See comments on lsort after test safe-zipfs-9.20. + test safe-zipfs-9.24 {interpConfigure change the access path; check module loading; stale data case 2 (worst case); zipfs} -setup { + set oldTm [tcl::tm::path list] + foreach path $oldTm { + tcl::tm::path remove $path + } + tcl::tm::path add [file join $ZipMountPoint auto0 modules] + } -body { + set i [safe::interpCreate -accessPath [list $tcl_library]] + + # Inspect. + set confA [safe::interpConfigure $i] + set sortA [mapAndSortList $PathMapp [dict get $confA -accessPath]] + set modsA [interp eval $i {tcl::tm::path list}] + set path0 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path1 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path2 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Force the interpreter to acquire pkg data which will soon become stale. + catch {interp eval $i {package require NOEXIST}} + catch {interp eval $i {package require mod1::NOEXIST}} + catch {interp eval $i {package require mod2::NOEXIST}} + + # Add to access path. + # This injects more tokens, pushing modules to higher token numbers. + safe::interpConfigure $i -accessPath [list $tcl_library \ + [file join $ZipMountPoint auto0 auto1] \ + [file join $ZipMountPoint auto0 auto2]] + # Inspect. + set confB [safe::interpConfigure $i] + set sortB [mapAndSortList $PathMapp [dict get $confB -accessPath]] + set modsB [interp eval $i {tcl::tm::path list}] + set path3 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules]] + set path4 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod1]] + set path5 [::safe::interpFindInAccessPath $i [file join $ZipMountPoint auto0 modules mod2]] + + # Try to load the packages and run a command from each one. + set code0 [catch {interp eval $i {package require test0}} msg0] + set code1 [catch {interp eval $i {package require mod1::test1}} msg1] + set code2 [catch {interp eval $i {package require mod2::test2}} msg2] + set out0 [interp eval $i {test0::try0}] + set out1 [interp eval $i {mod1::test1::try1}] + set out2 [interp eval $i {mod2::test2::try2}] + + list [lsort [list $path0 $path1 $path2]] -- $modsA -- \ + [lsort [list $path3 $path4 $path5]] -- $modsB -- \ + $code0 $msg0 $code1 $msg1 $code2 $msg2 -- $sortA -- $sortB -- \ + $out0 $out1 $out2 + } -cleanup { + tcl::tm::path remove [file join $ZipMountPoint auto0 modules] + foreach path [lreverse $oldTm] { + tcl::tm::path add $path + } + safe::interpDelete $i + } -match glob -result {{{$p(:1:)} {$p(:2:)} {$p(:3:)}} -- {{$p(:1:)}} --\ + {{$p(:3:)} {$p(:4:)} {$p(:5:)}} -- {{$p(:3:)}} --\ + 0 0.5 0 1.0 0 2.0 --\ + {TCLLIB ZIPDIR/auto0/modules ZIPDIR/auto0/modules/mod1\ + ZIPDIR/auto0/modules/mod2} --\ + {TCLLIB ZIPDIR/auto0/auto1 ZIPDIR/auto0/auto2 ZIPDIR/auto0/modules\ + ZIPDIR/auto0/modules/mod1 ZIPDIR/auto0/modules/mod2} --\ + res0 res1 res2} + # See comments on lsort after test safe-zipfs-9.20. + + # cleanup + set ::auto_path $SaveAutoPath + zipfs unmount ${ZipMountPoint} + unset SaveAutoPath TestsDir ZipMountPoint PathMapp + rename mapList {} + rename mapAndSortList {} + ::tcltest::cleanupTests + return +} [namespace current]] # Local Variables: # mode: tcl -- cgit v0.12 From c1cc3a72382efe93780f665b571785402f056366 Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 25 Apr 2021 13:58:22 +0000 Subject: Documenting our reference count management --- doc/CrtObjCmd.3 | 2 ++ doc/RecEvalObj.3 | 6 ++++++ doc/RegExp.3 | 16 ++++++++++++++++ doc/SetChanErr.3 | 15 ++++++++++++--- doc/SetResult.3 | 27 +++++++++++++++++++++++++++ doc/SetVar.3 | 21 +++++++++++++++++++++ doc/StringObj.3 | 27 +++++++++++++++++++++++++++ doc/SubstObj.3 | 7 +++++++ generic/tclIO.c | 24 ++++++++++++++---------- 9 files changed, 132 insertions(+), 13 deletions(-) diff --git a/doc/CrtObjCmd.3 b/doc/CrtObjCmd.3 index 854c057..59b217e 100644 --- a/doc/CrtObjCmd.3 +++ b/doc/CrtObjCmd.3 @@ -330,6 +330,8 @@ the values in its \fIobjv\fR argument will have a reference count of at least 1, with that guaranteed reference being from the Tcl evaluation stack. You should not call \fBTcl_DecrRefCount\fR on any of those values unless you call \fBTcl_IncrRefCount\fR on them first. +Also, when the \fIproc\fR is called, the interpreter result is +guaranteed to be an empty string value with a reference count of 1. .PP \fBTcl_GetCommandFullName\fR does not modify the reference count of its \fIobjPtr\fR argument, but does require that the object be unshared. diff --git a/doc/RecEvalObj.3 b/doc/RecEvalObj.3 index f9550a2..e68f4b5 100644 --- a/doc/RecEvalObj.3 +++ b/doc/RecEvalObj.3 @@ -44,6 +44,12 @@ allow the user to re-issue recently invoked commands. If the \fIflags\fR argument contains the \fBTCL_NO_EVAL\fR bit then the command is recorded without being evaluated. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The reference count of the \fIcmdPtr\fR argument to \fBTcl_RecordAndEvalObj\fR +must be at least 1. This function will modify the interpreter result; do not +use an existing result as \fIcmdPtr\fR directly without incrementing its +reference count. .SH "SEE ALSO" Tcl_EvalObjEx, Tcl_GetObjResult diff --git a/doc/RegExp.3 b/doc/RegExp.3 index aa757bc..0d60b8a 100644 --- a/doc/RegExp.3 +++ b/doc/RegExp.3 @@ -377,6 +377,22 @@ If no match was found, then it indicates the earliest point at which a match might occur if additional text is appended to the string. If it is no match is possible even with further text, this field will be set to \-1. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fItextObj\fR and \fIpatObj\fR arguments to \fBTcl_RegExpMatchObj\fR must +have reference counts of at least 1. Note however that this function may set +the interpreter result; neither argument should be the direct interpreter +result without an additional reference being taken. +.PP +The \fIpatObj\fR argument to \fBTcl_GetRegExpFromObj\fR must have a reference +count of at least 1. Note however that this function may set the interpreter +result; the argument should not be the direct interpreter result without an +additional reference being taken. +.PP +The \fItextObj\fR argument to \fBTcl_RegExpExecObj\fR must have a reference +count of at least 1. Note however that this function may set the interpreter +result; the argument should not be the direct interpreter result without an +additional reference being taken. .SH "SEE ALSO" re_syntax(n) .SH KEYWORDS diff --git a/doc/SetChanErr.3 b/doc/SetChanErr.3 index 5bb86be..473b61c 100644 --- a/doc/SetChanErr.3 +++ b/doc/SetChanErr.3 @@ -35,20 +35,20 @@ Refers to the Tcl interpreter whose bypass area is accessed. .AP Tcl_Obj* msg in Error message put into a bypass area. A list of return options and values, followed by a string message. Both message and the option/value information -are optional. +are optional. This \fImust\fR be a well-formed list. .AP Tcl_Obj** msgPtr out Reference to a place where the message stored in the accessed bypass area can be stored in. .BE .SH DESCRIPTION .PP -The current definition of a Tcl channel driver does not permit the direct +The standard definition of a Tcl channel driver does not permit the direct return of arbitrary error messages, except for the setting and retrieval of channel options. All other functions are restricted to POSIX error codes. .PP The functions described here overcome this limitation. Channel drivers are allowed to use \fBTcl_SetChannelError\fR and \fBTcl_SetChannelErrorInterp\fR -to place arbitrary error messages in \fBbypass areas\fR defined for channels +to place arbitrary error messages in \fIbypass areas\fR defined for channels and interpreters. And the generic I/O layer uses \fBTcl_GetChannelError\fR and \fBTcl_GetChannelErrorInterp\fR to look for messages in the bypass areas and arrange for their return as errors. The POSIX error codes set by a driver are @@ -134,6 +134,15 @@ leave all their error information in the interpreter result. .ta 1.9i 4i \fBTcl_Close\fR \fBTcl_UnstackChannel\fR \fBTcl_UnregisterChannel\fR .DE +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fImsg\fR argument to \fBTcl_SetChannelError\fR and +\fBTcl_SetChannelErrorInterp\fR, if not NULL, may have any reference count; +these functions will copy. +.PP +\fBTcl_GetChannelError\fR and \fBTcl_GetChannelErrorInterp\fR write a value +reference into their \fImsgPtr\fR, but do not manipulate its reference count. +The reference count will be at least 1 (unless the reference is NULL). .SH "SEE ALSO" Tcl_Close(3), Tcl_OpenFileChannel(3), Tcl_SetErrno(3) .SH KEYWORDS diff --git a/doc/SetResult.3 b/doc/SetResult.3 index 1622290..04a4b7f 100644 --- a/doc/SetResult.3 +++ b/doc/SetResult.3 @@ -239,6 +239,33 @@ typedef void \fBTcl_FreeProc\fR( .PP When \fIfreeProc\fR is called, its \fIblockPtr\fR will be set to the value of \fIresult\fR passed to \fBTcl_SetResult\fR. + +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The interpreter result is one of the main places that owns references to +values, along with the bytecode execution stack, argument lists, variables, +and the list and dictionary collection values. +.PP +\fBTcl_SetObjResult\fR takes a value with an arbitrary reference count +\fI(specifically including zero)\fR and guarantees to increment the reference +count. If code wishes to continue using the value after setting it as the +result, it should add its own reference to it with \fBTcl_IncrRefCount\fR. +.PP +\fBTcl_GetObjResult\fR returns the current interpreter result value. This will +have a reference count of at least 1. If the caller wishes to keep the +interpreter result value, it should increment its reference count. +.PP +\fBTcl_GetStringResult\fR does not manipulate reference counts, but the string +it returns is owned by (and has a lifetime controlled by) the current +interpreter result value; it should be copied instead of being relied upon to +persist after the next Tcl API call, as most Tcl operations can modify the +interpreter result. +.PP +\fBTcl_SetResult\fR, \fBTcl_AppendResult\fR, \fBTcl_AppendResultVA\fR, +\fBTcl_AppendElement\fR, and \fBTcl_ResetResult\fR all modify the interpreter +result. They may cause the old interpreter result to have its reference count +decremented and a new interpreter result to be allocated. After they have been +called, the reference count of the interpreter result is guaranteed to be 1. .SH "SEE ALSO" Tcl_AddErrorInfo, Tcl_CreateObjCommand, Tcl_SetErrorCode, Tcl_Interp, Tcl_GetReturnOptions diff --git a/doc/SetVar.3 b/doc/SetVar.3 index 4aa671a..eb8333b 100644 --- a/doc/SetVar.3 +++ b/doc/SetVar.3 @@ -242,6 +242,27 @@ but the array remains. If an array name is specified without an index, then the entire array is removed. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The result of \fBTcl_SetVar2Ex\fR, \fBTcl_ObjSetVar2\fR, \fBTcl_GetVar2Ex\fR, +and \fBTcl_ObjGetVar2\fR is (if non-NULL) a value with a reference of at least +1, where that reference is held by the variable that the function has just +operated upon. +.PP +The \fInewValuePtr\fR argument to \fBTcl_SetVar2Ex\fR and \fBTcl_ObjSetVar2\fR +may be an arbitrary reference count value; its reference count will be +incremented on success. However, it is recommended to not use a zero reference +count value, as that makes correct handling of the error case tricky. +.PP +The \fIpart1\fR argument to \fBTcl_ObjSetVar2\fR and \fBTcl_ObjGetVar2\fR can +have any reference count; these functions never modify it. It is recommended +to not use a zero reference count for this argument. +.PP +The \fIpart2\fR argument to \fBTcl_ObjSetVar2\fR and \fBTcl_ObjGetVar2\fR, if +non-NULL, should not have a zero reference count as these functions may +retain a reference to it (particularly when it is used to create an array +element that did not previously exist). + .SH "SEE ALSO" Tcl_GetObjResult, Tcl_GetStringResult, Tcl_TraceVar diff --git a/doc/StringObj.3 b/doc/StringObj.3 index c55f57d..22e872a 100644 --- a/doc/StringObj.3 +++ b/doc/StringObj.3 @@ -383,6 +383,33 @@ white space, then that value is ignored entirely. This white-space removal was added to make the output of the \fBconcat\fR command cleaner-looking. \fBTcl_ConcatObj\fR returns a pointer to a newly-created value whose ref count is zero. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_NewStringObj\fR, \fBTcl_NewUnicodeObj\fB, \fBTcl_Format\fR, +\fBTcl_ObjPrintf\fR, and \fBTcl_ConcatObj\fR always return a zero-reference +object, much like \fBTcl_NewObj\fR. +.PP +\fBTcl_GetStringFromObj\fR, \fBTcl_GetString\fR, \fBTcl_GetUnicodeFromObj\fR, +\fBTcl_GetUnicode\fR, \fBTcl_GetUniChar\fR, \fBTcl_GetCharLength\fR, and +\fBTcl_GetRange\fR all only work with an existing value; they do not +manipulate its reference count in any way. +.PP +\fBTcl_SetStringObj\fR, \fBTcl_SetUnicodeObj\fR, \fBTcl_AppendToObj\fR, +\fBTcl_AppendUnicodeToObj\fR, \fBTcl_AppendObjToObj\fR, +\fBTcl_AppendStringsToObj\fR, \fBTcl_AppendStringsToObjVA\fR, +\fBTcl_AppendLimitedToObj\fR, \fBTcl_AppendFormatToObj\fR, +\fBTcl_AppendPrintfToObj\fR, \fBTcl_SetObjLength\fR, and +\fBTcl_AttemptSetObjLength\fR and require their \fIobjPtr\fR to be an unshared +value (i.e, a reference count no more than 1) as they will modify it. +.PP +Additional arguments to the above functions (the \fIappendObjPtr\fR argument +to \fBTcl_AppendObjToObj\fR, values in the \fIobjv\fR argument to +\fBTcl_Format\fR, \fBTcl_AppendFormatToObj\fR, and \fBTcl_ConcatObj\fR) can +have any reference count, but reference counts of zero are not recommended. +.PP +\fBTcl_Format\fR and \fBTcl_AppendFormatToObj\fR may modify the interpreter +result, which involves changing the reference count of a value. + .SH "SEE ALSO" Tcl_NewObj(3), Tcl_IncrRefCount(3), Tcl_DecrRefCount(3), format(n), sprintf(3) .SH KEYWORDS diff --git a/doc/SubstObj.3 b/doc/SubstObj.3 index a2b6214..fa30fb1 100644 --- a/doc/SubstObj.3 +++ b/doc/SubstObj.3 @@ -62,6 +62,13 @@ result of the whole substitution on \fIobjPtr\fR will be truncated at the point immediately before the start of the command substitution, and no characters will be added to the result or substitutions performed after that point. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fIobjPtr\fR argument to \fBTcl_SubstObj\fR must not have a reference +count of zero. This function modifies the interpreter result, both on success +and on failure; the result of this function on success is exactly the current +interpreter result. Successful results should have their reference count +incremented if they are to be retained. .SH "SEE ALSO" subst(n) .SH KEYWORDS diff --git a/generic/tclIO.c b/generic/tclIO.c index 3954af2..fd87520 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -10966,15 +10966,17 @@ Tcl_SetChannelErrorInterp( Tcl_Obj *msg) /* Error message to store. */ { Interp *iPtr = (Interp *) interp; - - if (iPtr->chanMsg != NULL) { - TclDecrRefCount(iPtr->chanMsg); - iPtr->chanMsg = NULL; - } + Tcl_Obj *disposePtr = iPtr->chanMsg; if (msg != NULL) { iPtr->chanMsg = FixLevelCode(msg); Tcl_IncrRefCount(iPtr->chanMsg); + } else { + iPtr->chanMsg = NULL; + } + + if (disposePtr != NULL) { + TclDecrRefCount(disposePtr); } return; } @@ -11002,15 +11004,17 @@ Tcl_SetChannelError( Tcl_Obj *msg) /* Error message to store. */ { ChannelState *statePtr = ((Channel *) chan)->state; - - if (statePtr->chanMsg != NULL) { - TclDecrRefCount(statePtr->chanMsg); - statePtr->chanMsg = NULL; - } + Tcl_Obj *disposePtr = statePtr->chanMsg; if (msg != NULL) { statePtr->chanMsg = FixLevelCode(msg); Tcl_IncrRefCount(statePtr->chanMsg); + } else { + statePtr->chanMsg = NULL; + } + + if (disposePtr != NULL) { + TclDecrRefCount(disposePtr); } return; } -- cgit v0.12 From 5003483b1d2f1aec9e404ab053ca9b00da9f087a Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 25 Apr 2021 15:24:14 +0000 Subject: Documenting our reference count management --- doc/TclZlib.3 | 25 +++++++++++++++++++++++++ doc/Tcl_Main.3 | 9 +++++++++ doc/WrongNumArgs.3 | 6 ++++++ 3 files changed, 40 insertions(+) diff --git a/doc/TclZlib.3 b/doc/TclZlib.3 index 4a5df89..4d06923 100644 --- a/doc/TclZlib.3 +++ b/doc/TclZlib.3 @@ -262,6 +262,31 @@ file named by the \fBfilename\fR field was modified. Suitable for use with . The type of the uncompressed data (either \fBbinary\fR or \fBtext\fR) if known. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_ZlibDeflate\fR and \fBTcl_ZlibInflate\fR take a value with arbitrary +reference count for their \fIdataObj\fR and \fIdictObj\fR arguments (the +latter often being NULL instead), and set the interpreter result with their +output value (or an error). The existing interpreter result should not be +passed as any argument value unless an additional reference is held. +.PP +\fBTcl_ZlibStreamInit\fR takes a value with arbitrary reference count for its +\fIdictObj\fR argument; it only reads from it. The existing interpreter result +should not be passed unless an additional reference is held. +.PP +\fBTcl_ZlibStreamGetCommandName\fR returns a zero reference count value, much +like \fBTcl_NewObj\fR. +.PP +The \fIdataObj\fR argument to \fBTcl_ZlibStreamPut\fR is a value with +arbitrary reference count; it is only ever read from. +.PP +The \fIdataObj\fR argument to \fBTcl_ZlibStreamGet\fR is an unshared value +(see \fBTcl_IsShared\fR) that will be updated by the function. +.PP +The \fIcompDict\fR argument to \fBTcl_ZlibStreamSetCompressionDictionary\fR, +if non-NULL, may be duplicated or may have its reference count incremented. +Using a zero reference count value is not recommended. + .SH "PORTABILITY NOTES" These functions will fail gracefully if Tcl is not linked with the zlib library. diff --git a/doc/Tcl_Main.3 b/doc/Tcl_Main.3 index 5817c10..904ecbe 100644 --- a/doc/Tcl_Main.3 +++ b/doc/Tcl_Main.3 @@ -201,6 +201,15 @@ procedure (if any) returns, \fBTcl_Main\fR will also evaluate the \fBexit\fR command. .PP \fBTcl_Main\fR can not be used in stub-enabled extensions. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +\fBTcl_SetStartupScript\fR takes a value (or NULL) for its \fIpath\fR +argument, and will increment the reference count of it. +.PP +\fBTcl_GetStartupScript\fR returns a value with reference count at least 1, or +NULL. It's \fIencodingPtr\fR is also used (if non-NULL) to return a value with +a reference count at least 1, or NULL. In both cases, the owner of the values +is the current thread. .SH "SEE ALSO" tclsh(1), Tcl_GetStdChannel(3), Tcl_StandardChannels(3), Tcl_AppInit(3), exit(n), encoding(n) diff --git a/doc/WrongNumArgs.3 b/doc/WrongNumArgs.3 index 93e2ebb..533cb4f 100644 --- a/doc/WrongNumArgs.3 +++ b/doc/WrongNumArgs.3 @@ -73,6 +73,12 @@ is now an \fIindexObject\fR because it was passed to .CS wrong # args: should be "foo barfly fileName count" .CE +.SH "REFERENCE COUNT MANAGEMENT" +.PP +The \fIobjv\fR argument to \fBTcl_WrongNumArgs\fR should be the exact +arguments passed to the command or method implementation function that is +calling \fBTcl_WrongNumArgs\fR. As such, all values referenced in it should +have reference counts greater than zero; this is usually a non-issue. .SH "SEE ALSO" Tcl_GetIndexFromObj(3) .SH KEYWORDS -- cgit v0.12 From 9bf722e8da25eb28ed58fe97f941f4badaa3af36 Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 25 Apr 2021 20:21:07 +0000 Subject: Documenting our reference count management --- doc/TraceVar.3 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/TraceVar.3 b/doc/TraceVar.3 index 82aa7b8..7751cf7 100644 --- a/doc/TraceVar.3 +++ b/doc/TraceVar.3 @@ -359,6 +359,14 @@ Traces on a variable are always removed whenever the variable is deleted; the only time \fBTCL_TRACE_DESTROYED\fR is not set is for a whole-array trace invoked when only a single element of an array is unset. +.SH "REFERENCE COUNT MANAGEMENT" +.PP +When a \fIproc\fR callback is invoked, and that callback was installed with +the \fBTCL_TRACE_RESULT_OBJECT\fR flag, the result of the callback is a +Tcl_Obj reference when there is an error. The result will have its reference +count decremented once when no longer needed, or may have additional +references made to it (e.g., by setting it as the interpreter result with +\fBTcl_SetObjResult\fR). .SH BUGS .PP Array traces are not yet integrated with the Tcl \fBinfo exists\fR command, -- cgit v0.12 From f2e08d25dbc3a64b9197ac4477e972d6109cf11b Mon Sep 17 00:00:00 2001 From: pooryorick Date: Mon, 26 Apr 2021 22:48:15 +0000 Subject: Add test constaints "debug", "purify", and "debugpurify" --- generic/tclTest.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/tcltests.tcl | 10 ++++++++ 2 files changed, 84 insertions(+) diff --git a/generic/tclTest.c b/generic/tclTest.c index 39bd392..99fe92f 100644 --- a/generic/tclTest.c +++ b/generic/tclTest.c @@ -227,6 +227,7 @@ static Tcl_CmdProc TestcreatecommandCmd; static Tcl_CmdProc TestdcallCmd; static Tcl_CmdProc TestdelCmd; static Tcl_CmdProc TestdelassocdataCmd; +static Tcl_ObjCmdProc TestdebugObjCmd; static Tcl_ObjCmdProc TestdoubledigitsObjCmd; static Tcl_CmdProc TestdstringCmd; static Tcl_ObjCmdProc TestencodingObjCmd; @@ -265,6 +266,7 @@ static Tcl_ObjCmdProc TestparsevarObjCmd; static Tcl_ObjCmdProc TestparsevarnameObjCmd; static Tcl_ObjCmdProc TestpreferstableObjCmd; static Tcl_ObjCmdProc TestprintObjCmd; +static Tcl_ObjCmdProc TestpurifyObjCmd; static Tcl_ObjCmdProc TestregexpObjCmd; static Tcl_ObjCmdProc TestreturnObjCmd; static void TestregexpXflags(const char *string, @@ -504,6 +506,8 @@ Tcltest_Init( Tcl_CreateCommand(interp, "testcreatecommand", TestcreatecommandCmd, NULL, NULL); Tcl_CreateCommand(interp, "testdcall", TestdcallCmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "testdebug", TestdebugObjCmd, + NULL, NULL); Tcl_CreateCommand(interp, "testdel", TestdelCmd, NULL, NULL); Tcl_CreateCommand(interp, "testdelassocdata", TestdelassocdataCmd, NULL, NULL); @@ -568,6 +572,8 @@ Tcltest_Init( NULL, NULL); Tcl_CreateObjCommand(interp, "testpreferstable", TestpreferstableObjCmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "testpurify", TestpurifyObjCmd, + NULL, NULL); Tcl_CreateObjCommand(interp, "testprint", TestprintObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testregexp", TestregexpObjCmd, @@ -3364,6 +3370,40 @@ TestlocaleCmd( /* *---------------------------------------------------------------------- * + * TestdebugObjCmd -- + * + * Implements the "testdebug" command, to detect whether Tcl was built with + * --enabble-symbols. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TestdebugObjCmd( + TCL_UNUSED(void *), + Tcl_Interp *interp, /* Current interpreter. */ + TCL_UNUSED(int) /*objc*/, + TCL_UNUSED(Tcl_Obj *const *) /*objv*/) +{ + +#if defined(NDEBUG) && NDEBUG == 1 + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0)); +#else + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1)); +#endif + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * CleanupTestSetassocdataTests -- * * This function is called when an interpreter is deleted to clean @@ -3765,6 +3805,40 @@ TestprintObjCmd( /* *---------------------------------------------------------------------- * + * TestpurifyObjCmd -- + * + * Implements the "testpurify" command, to detect whether Tcl was built with + * -DPURIFY. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TestpurifyObjCmd( + TCL_UNUSED(void *), + Tcl_Interp *interp, /* Current interpreter. */ + TCL_UNUSED(int) /*objc*/, + TCL_UNUSED(Tcl_Obj *const *) /*objv*/) +{ + +#ifdef PURIFY + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1)); +#else + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0)); +#endif + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * TestregexpObjCmd -- * * This procedure implements the "testregexp" command. It is used to give diff --git a/tests/tcltests.tcl b/tests/tcltests.tcl index 193ba0a..09c8b28 100644 --- a/tests/tcltests.tcl +++ b/tests/tcltests.tcl @@ -3,6 +3,16 @@ package require tcltest 2.5 namespace import ::tcltest::* testConstraint exec [llength [info commands exec]] +testConstraint debug [testdebug] +testConstraint purify [testpurify] +testConstraint debugpurify [ + expr { + ![stestConstraint memory] + && + [testConstraint debug] + && + [testConstraint purify] +}] testConstraint fcopy [llength [info commands fcopy]] testConstraint fileevent [llength [info commands fileevent]] testConstraint thread [ -- cgit v0.12 From 28437b46c38ff3c6528d51c7d5e11e35a2d64df4 Mon Sep 17 00:00:00 2001 From: pooryorick Date: Mon, 26 Apr 2021 22:53:42 +0000 Subject: Add io-28.6 an io-28.7 to test closing a channel within a channel event handler. --- tests/io.test | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/tests/io.test b/tests/io.test index e0a2389..ddf2403 100644 --- a/tests/io.test +++ b/tests/io.test @@ -2346,9 +2346,9 @@ test io-28.3 {CloseChannel, not called before output queue is empty} \ set result ok } } ok -test io-28.4 {Tcl_Close} {testchannel} { +test io-28.4 Tcl_Close testchannel { file delete $path(test1) - set l "" + set l {} lappend l [lsort [testchannel open]] set f [open $path(test1) w] lappend l [lsort [testchannel open]] @@ -2373,6 +2373,74 @@ test io-28.5 {Tcl_Close vs standard handles} {stdio unix testchannel} { lsort $l } {file1 file2} + +test io-28.6 { + close channel in write event handler + + Should not produce a segmentation fault in a Tcl built with + --enable-symbols and -DPURIFY +} debugpurify { + variable done + variable res + after 0 [list coroutine c1 apply [list {} { + variable done + set chan [chan create w {apply {args { + list initialize finalize watch write configure blocking + }}}] + chan configure $chan -blocking 0 + while 1 { + chan event $chan writable [list [info coroutine]] + yield + close $chan + set done 1 + return + } + } [namespace current]]] + vwait [namespace current]::done +return success +} success + + +test io-28.7 { + close channel in read event handler + + Should not produce a segmentation fault in a Tcl built with + --enable-symbols and -DPURIFY +} debugpurify { + variable done + variable res + after 0 [list coroutine c1 apply [list {} { + variable done + set chan [chan create r {apply {{cmd chan args} { + switch $cmd { + blocking - finalize { + } + watch { + chan postevent $chan read + } + initialize { + list initialize finalize watch read write configure blocking + } + default { + error [list {unexpected command} $cmd] + } + } + }}}] + chan configure $chan -blocking 0 + while 1 { + chan event $chan readable [list [info coroutine]] + yield + close $chan + set done 1 + return + } + } [namespace current]]] + vwait [namespace current]::done +return success +} success + + + test io-29.1 {Tcl_WriteChars, channel not writable} { list [catch {puts stdin hello} msg] $msg } {1 {channel "stdin" wasn't opened for writing}} @@ -5310,9 +5378,6 @@ test io-39.1 {Tcl_GetChannelOption} { close $f1 set x } 1 -# -# Test 17.2 was removed. -# test io-39.2 {Tcl_GetChannelOption} { file delete $path(test1) set f1 [open $path(test1) w] -- cgit v0.12 From b6d1ba3fdb3cf3af7b1d63e37b2bb3e49c18f84f Mon Sep 17 00:00:00 2001 From: pooryorick Date: Mon, 26 Apr 2021 23:01:03 +0000 Subject: Fix typo in tcltests.tcl. --- tests/tcltests.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tcltests.tcl b/tests/tcltests.tcl index 09c8b28..4dea64f 100644 --- a/tests/tcltests.tcl +++ b/tests/tcltests.tcl @@ -7,7 +7,7 @@ testConstraint debug [testdebug] testConstraint purify [testpurify] testConstraint debugpurify [ expr { - ![stestConstraint memory] + ![testConstraint memory] && [testConstraint debug] && -- cgit v0.12 From 81d499ea3132322afb4b7b97c56a949ad04609ac Mon Sep 17 00:00:00 2001 From: pooryorick Date: Mon, 26 Apr 2021 23:06:14 +0000 Subject: Fix for io-28.6. --- generic/tclIO.c | 40 ++++++++++++++++++++++++---------------- generic/tclIORChan.c | 8 ++++---- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index fd87520..b9ec2a7 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -3660,7 +3660,7 @@ Tcl_CloseEx( * That won't do. */ - if (statePtr->flags & CHANNEL_INCLOSE) { + if (GotFlag(statePtr, CHANNEL_INCLOSE)) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "illegal recursive call to close through close-handler" @@ -8605,9 +8605,12 @@ ChannelTimerProc( ClientData clientData) { Channel *chanPtr = (Channel *)clientData; + + /* State info for channel */ ChannelState *statePtr = chanPtr->state; - /* State info for channel */ + /* Preserve chanPtr to guard against deallocation in Tcl_NotifyChannel. */ + TclChannelPreserve((Tcl_Channel)chanPtr); Tcl_Preserve(statePtr); statePtr->timer = NULL; if (statePtr->interestMask & TCL_WRITABLE @@ -8623,22 +8626,27 @@ ChannelTimerProc( Tcl_NotifyChannel((Tcl_Channel) chanPtr, TCL_WRITABLE); } - if (!GotFlag(statePtr, CHANNEL_NEED_MORE_DATA) - && (statePtr->interestMask & TCL_READABLE) - && (statePtr->inQueueHead != NULL) - && IsBufferReady(statePtr->inQueueHead)) { - /* - * Restart the timer in case a channel handler reenters the event loop - * before UpdateInterest gets called by Tcl_NotifyChannel. - */ + /* The channel may have just been closed from within Tcl_NotifyChannel */ + if (!GotFlag(statePtr, CHANNEL_INCLOSE)) { + if (!GotFlag(statePtr, CHANNEL_NEED_MORE_DATA) + && (statePtr->interestMask & TCL_READABLE) + && (statePtr->inQueueHead != NULL) + && IsBufferReady(statePtr->inQueueHead)) { + /* + * Restart the timer in case a channel handler reenters the event loop + * before UpdateInterest gets called by Tcl_NotifyChannel. + */ - statePtr->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, - ChannelTimerProc,chanPtr); - Tcl_NotifyChannel((Tcl_Channel) chanPtr, TCL_READABLE); - } else { - UpdateInterest(chanPtr); + statePtr->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, + ChannelTimerProc,chanPtr); + Tcl_NotifyChannel((Tcl_Channel) chanPtr, TCL_READABLE); + } else { + UpdateInterest(chanPtr); + } } + Tcl_Release(statePtr); + TclChannelRelease((Tcl_Channel)chanPtr); } /* @@ -8703,7 +8711,7 @@ Tcl_CreateChannelHandler( /* * The remainder of the initialization below is done regardless of whether - * or not this is a new record or a modification of an old one. + * this is a new record or a modification of an old one. */ chPtr->mask = mask; diff --git a/generic/tclIORChan.c b/generic/tclIORChan.c index 88f6de8..9097bf4 100644 --- a/generic/tclIORChan.c +++ b/generic/tclIORChan.c @@ -80,7 +80,7 @@ static const Tcl_ChannelType tclRChannelType = { ReflectGetOption, /* Get options. NULL'able */ ReflectWatch, /* Initialize notifier */ NULL, /* Get OS handle from the channel. NULL'able */ - ReflectClose, /* No close2 support. NULL'able */ + ReflectClose, /* No close2 support. NULL'able */ ReflectBlock, /* Set blocking/nonblocking. NULL'able */ NULL, /* Flush channel. Not used by core. NULL'able */ NULL, /* Handle events. NULL'able */ @@ -1156,7 +1156,7 @@ TclChanCaughtErrorBypass( * ReflectClose -- * * This function is invoked when the channel is closed, to delete the - * driver specific instance data. + * driver-specific instance data. * * Results: * A posix error. @@ -1189,8 +1189,8 @@ ReflectClose( /* * This call comes from TclFinalizeIOSystem. There are no * interpreters, and therefore we cannot call upon the handler command - * anymore. Threading is irrelevant as well. We simply clean up all - * our C level data structures and leave the Tcl level to the other + * anymore. Threading is irrelevant as well. Simply clean up all + * the C level data structures and leave the Tcl level to the other * finalization functions. */ -- cgit v0.12 From c7fcbf756c0b7f4332a6d930cfec58945029eacd Mon Sep 17 00:00:00 2001 From: pooryorick Date: Tue, 27 Apr 2021 10:48:58 +0000 Subject: Make tcltests.tcl continue to function even if tclTest.c isn't available. --- tests/tcltests.tcl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/tcltests.tcl b/tests/tcltests.tcl index 4dea64f..1ee37d3 100644 --- a/tests/tcltests.tcl +++ b/tests/tcltests.tcl @@ -3,16 +3,18 @@ package require tcltest 2.5 namespace import ::tcltest::* testConstraint exec [llength [info commands exec]] -testConstraint debug [testdebug] -testConstraint purify [testpurify] -testConstraint debugpurify [ - expr { - ![testConstraint memory] - && - [testConstraint debug] - && - [testConstraint purify] -}] +if {[namespace which testdebug] ne {}} { + testConstraint debug [testdebug] + testConstraint purify [testpurify] + testConstraint debugpurify [ + expr { + ![testConstraint memory] + && + [testConstraint debug] + && + [testConstraint purify] + }] +} testConstraint fcopy [llength [info commands fcopy]] testConstraint fileevent [llength [info commands fileevent]] testConstraint thread [ -- cgit v0.12 From ef9154f1436449e65af48e6356b80877668e349e Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Wed, 28 Apr 2021 12:42:17 +0000 Subject: Rename TclSetPreInitScript() to Tcl_SetPreInitScript(). Tiny part of TIP #596, the only part meant for 8.7. --- doc/Init.3 | 14 +++++++++++++- generic/tcl.h | 1 + generic/tclInt.decls | 4 ++-- generic/tclIntDecls.h | 10 ++++++---- generic/tclInterp.c | 6 +++--- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/doc/Init.3 b/doc/Init.3 index d9fc2e1..cf17a37 100644 --- a/doc/Init.3 +++ b/doc/Init.3 @@ -2,7 +2,7 @@ '\" Copyright (c) 1998-2000 Scriptics Corporation. '\" All rights reserved. '\" -.TH Tcl_Init 3 8.0 Tcl "Tcl Library Procedures" +.TH Tcl_Init 3 8.7 Tcl "Tcl Library Procedures" .so man.macros .BS .SH NAME @@ -13,10 +13,15 @@ Tcl_Init \- find and source initialization script .sp int \fBTcl_Init\fR(\fIinterp\fR) +.sp +const char * +\fBTcl_SetPreInitScript\fR(\fIscriptPtr\fR) .SH ARGUMENTS .AS Tcl_Interp *interp .AP Tcl_Interp *interp in Interpreter to initialize. +.AP "const char" *scriptPtr in +Address of the initialization script. .BE .SH DESCRIPTION @@ -26,6 +31,13 @@ Interpreter to initialize. path. .PP \fBTcl_Init\fR is typically called from \fBTcl_AppInit\fR procedures. +.PP +\fBTcl_SetPreInitScript\fR registers the pre-initialization script and +returns the former (now replaced) script pointer. +A value of \fINULL\fR may be passed to not register any script. +The pre-initialization script is executed by \fBTcl_Init\fR before accessing +the file system. The purpose is to typically prepare a custom file system +(like an embedded zip-file) to be activated before the search. .SH "SEE ALSO" Tcl_AppInit, Tcl_Main diff --git a/generic/tcl.h b/generic/tcl.h index 507342f..4d4e2b7 100644 --- a/generic/tcl.h +++ b/generic/tcl.h @@ -2385,6 +2385,7 @@ EXTERN const char * Tcl_PkgInitStubsCheck(Tcl_Interp *interp, const char *version, int exact); EXTERN void Tcl_InitSubsystems(void); EXTERN void Tcl_GetMemoryInfo(Tcl_DString *dsPtr); +EXTERN CONST86 char *Tcl_SetPreInitScript(const char *string); #ifndef TCL_NO_DEPRECATED # define Tcl_StaticPackage Tcl_StaticLibrary #endif diff --git a/generic/tclInt.decls b/generic/tclInt.decls index c7ead64..aad7db3 100644 --- a/generic/tclInt.decls +++ b/generic/tclInt.decls @@ -184,7 +184,7 @@ declare 41 { Tcl_Command TclGetOriginalCommand(Tcl_Command command) } declare 42 { - CONST86 char *TclpGetUserHome(const char *name, Tcl_DString *bufferPtr) + const char *TclpGetUserHome(const char *name, Tcl_DString *bufferPtr) } # Removed in 8.5a2: #declare 43 { @@ -409,7 +409,7 @@ declare 98 { # Tcl_Obj *objPtr, int flags) #} declare 101 { - CONST86 char *TclSetPreInitScript(const char *string) + const char *TclSetPreInitScript(const char *string) } declare 102 { void TclSetupEnv(Tcl_Interp *interp) diff --git a/generic/tclIntDecls.h b/generic/tclIntDecls.h index bfd3102..5ae92e4 100644 --- a/generic/tclIntDecls.h +++ b/generic/tclIntDecls.h @@ -153,7 +153,7 @@ EXTERN int TclGetOpenMode(Tcl_Interp *interp, const char *str, /* 41 */ EXTERN Tcl_Command TclGetOriginalCommand(Tcl_Command command); /* 42 */ -EXTERN CONST86 char * TclpGetUserHome(const char *name, +EXTERN const char * TclpGetUserHome(const char *name, Tcl_DString *bufferPtr); /* Slot 43 is reserved */ /* 44 */ @@ -264,7 +264,7 @@ EXTERN int TclServiceIdle(void); /* Slot 99 is reserved */ /* Slot 100 is reserved */ /* 101 */ -EXTERN CONST86 char * TclSetPreInitScript(const char *string); +EXTERN const char * TclSetPreInitScript(const char *string); /* 102 */ EXTERN void TclSetupEnv(Tcl_Interp *interp); /* 103 */ @@ -710,7 +710,7 @@ typedef struct TclIntStubs { TclObjCmdProcType (*tclGetObjInterpProc) (void); /* 39 */ int (*tclGetOpenMode) (Tcl_Interp *interp, const char *str, int *seekFlagPtr); /* 40 */ Tcl_Command (*tclGetOriginalCommand) (Tcl_Command command); /* 41 */ - CONST86 char * (*tclpGetUserHome) (const char *name, Tcl_DString *bufferPtr); /* 42 */ + const char * (*tclpGetUserHome) (const char *name, Tcl_DString *bufferPtr); /* 42 */ void (*reserved43)(void); int (*tclGuessPackageName) (const char *fileName, Tcl_DString *bufPtr); /* 44 */ int (*tclHideUnsafeCommands) (Tcl_Interp *interp); /* 45 */ @@ -769,7 +769,7 @@ typedef struct TclIntStubs { int (*tclServiceIdle) (void); /* 98 */ void (*reserved99)(void); void (*reserved100)(void); - CONST86 char * (*tclSetPreInitScript) (const char *string); /* 101 */ + const char * (*tclSetPreInitScript) (const char *string); /* 101 */ void (*tclSetupEnv) (Tcl_Interp *interp); /* 102 */ int (*tclSockGetPort) (Tcl_Interp *interp, const char *str, const char *proto, int *portPtr); /* 103 */ TCL_DEPRECATED_API("") int (*tclSockMinimumBuffersOld) (int sock, int size); /* 104 */ @@ -1415,7 +1415,9 @@ extern const TclIntStubs *tclIntStubsPtr; #undef TclGuessPackageName #undef TclUnusedStubEntry +#undef TclSetPreInitScript #ifndef TCL_NO_DEPRECATED +# define TclSetPreInitScript Tcl_SetPreInitScript # define TclGuessPackageName(fileName, pkgName) ((void)fileName,(void)pkgName,0) #endif diff --git a/generic/tclInterp.c b/generic/tclInterp.c index d63add2..fd90abc 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -281,7 +281,7 @@ static Tcl_ObjCmdProc NRChildCmd; /* *---------------------------------------------------------------------- * - * TclSetPreInitScript -- + * Tcl_SetPreInitScript -- * * This routine is used to change the value of the internal variable, * tclPreInitScript. @@ -296,12 +296,12 @@ static Tcl_ObjCmdProc NRChildCmd; */ const char * -TclSetPreInitScript( +Tcl_SetPreInitScript( const char *string) /* Pointer to a script. */ { const char *prevString = tclPreInitScript; tclPreInitScript = string; - return(prevString); + return prevString; } /* -- cgit v0.12 From ce299372a06ae6d59404bce267dcd29d38543fa1 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Wed, 28 Apr 2021 13:04:27 +0000 Subject: CONST86 -> const --- generic/tcl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tcl.h b/generic/tcl.h index 4d4e2b7..577f1cb 100644 --- a/generic/tcl.h +++ b/generic/tcl.h @@ -2385,7 +2385,7 @@ EXTERN const char * Tcl_PkgInitStubsCheck(Tcl_Interp *interp, const char *version, int exact); EXTERN void Tcl_InitSubsystems(void); EXTERN void Tcl_GetMemoryInfo(Tcl_DString *dsPtr); -EXTERN CONST86 char *Tcl_SetPreInitScript(const char *string); +EXTERN const char * Tcl_SetPreInitScript(const char *string); #ifndef TCL_NO_DEPRECATED # define Tcl_StaticPackage Tcl_StaticLibrary #endif -- cgit v0.12 From 71dcf14194a52565764654ad3039af42c094e8e8 Mon Sep 17 00:00:00 2001 From: pooryorick Date: Wed, 28 Apr 2021 14:01:22 +0000 Subject: Add test for upvar a linked variable to itself. --- tests/upvar.test | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/upvar.test b/tests/upvar.test index 82079b1..c31eaa1 100644 --- a/tests/upvar.test +++ b/tests/upvar.test @@ -249,6 +249,33 @@ test upvar-6.3 {retargeting an upvar} { p1 } {abcde 44} + + +test upvar-6.4 { + retargeting a variable created by upvar to itself is allowed +} -body { + catch { + unset x + } + catch { + unset y + } + set res {} + set x abcde + set res [catch { + upvar 0 x x + } cres copts] + lappend res [dict get $copts -errorcode] + upvar 0 x y + lappend res $y + upvar 0 y y + lappend res $y + return $res +} -cleanup { + upvar 0 {} y +} -result {1 {TCL UPVAR SELF} abcde abcde} + + test upvar-7.1 {upvar to same level} { set x 44 set y 55 -- cgit v0.12