TclInvalidateStringRep()是否应该重置长度?

时间:2019-01-24 00:33:05

标签: tcl

我对TCL 8.6.8源tclInt.h中的以下代码有疑问:

4277 #define TclInvalidateStringRep(objPtr) \
4278     if (objPtr->bytes != NULL) { \
4279         if (objPtr->bytes != tclEmptyStringRep) { \
4280             ckfree((char *) objPtr->bytes); \
4281         } \
4282         objPtr->bytes = NULL; \
4283     }

此宏由tclObj.c中的Tcl_InvalidateStringRep()调用。

我的疑问是,为什么不将tclObj的长度重置为零?

这来自Tcl_Obj的定义:

 808 typedef struct Tcl_Obj {
 809     int refCount;               /* When 0 the object will be freed. */
 810     char *bytes;                /* This points to the first byte of the
 811                                  * object's string representation. The array
 812                                  * must be followed by a null byte (i.e., at
 813                                  * offset length) but may also contain
 814                                  * embedded null characters. The array's
 815                                  * storage is allocated by ckalloc. NULL means
 816                                  * the string rep is invalid and must be
 817                                  * regenerated from the internal rep.  Clients
 818                                  * should use Tcl_GetStringFromObj or
 819                                  * Tcl_GetString to get a pointer to the byte
 820                                  * array as a readonly value. */
 821     int length;                 /* The number of bytes at *bytes, not
 822                                  * including the terminating null. */

所以您可以看到长度与字节紧密相关,当清除字节时,我们不应该重置长度吗?

我的疑问来自以下代码,tclLiteral.c中的TclCreateLiteral():

 200     for (globalPtr=globalTablePtr->buckets[globalHash] ; globalPtr!=NULL;
 201             globalPtr = globalPtr->nextPtr) {
 202         objPtr = globalPtr->objPtr;
 203         if ((globalPtr->nsPtr == nsPtr)
 204                 && (objPtr->length == length) && ((length == 0)
 205                 || ((objPtr->bytes[0] == bytes[0])
 206                 && (memcmp(objPtr->bytes, bytes, (unsigned) length) == 0)))) {

所以在第204行,当长度不为零而字节为NULL时,程序崩溃。

我的产品包括TCL源,当我跟踪程序崩溃时发现上述问题。我将变通方法放入了我们的代码中,但想与社区确认是否确实存在漏洞。

2 个答案:

答案 0 :(得分:0)

我已经考虑过这一点,尽管我认为清除表示的代码这样做是错误的(因为原则上应该共享对象,因此不应观察到更改),但我当然认为很难证明这是不可能的。当然,TclCreateLiteral中的tclLiteral.c不会爆炸!

The fix我正在使用的是使TclCreateLiteral使用TclGetStringFromObjTcl_GetStringFromObj的Tcl内部宏版本)来获取byteslength字段,而不是直接使用它们,以便保留正确的约束。 这将使字符串表示形式再次存在(如果将其删除)。如果代码继续崩溃,则问题在于您的代码正在文字上调用TclInvalidateStringRep(并设置了不能为此生成一个字符串; Tcl包含其中的一些字符串,但这是因为它从未从中清除原始字符串)。

请记住,Tcl_Obj仅在错误时才应清除其字符串rep,而不仅仅是在获得非字符串表示形式时。值已被解释为整数的事实并不意味着不应将其解释为列表(正好相反!),并且如果内部表示形式从未更新为其他值(就地修改仅应碰巧出现在未共享的对象上),它根本不需要丢失该字符串表示形式。

答案 1 :(得分:0)

您的方法在某处似乎是错误的。

对于没有引用(TclInvalidateStringRep)或只有一个引用(so refCount == 0)的对象,基本上允许refCount <= 1的调用,然后只有在您确定此1的情况下,才可以调用参考仅供您参考。

Tcl的共享对象可以切换其内部表示,但是字符串表示保持不变。否则,您将破坏Tcl的基本原理(例如EIAS等)。

最简单的示例可以解释这一点:

set k 0x7f
dict set d $k test
expr {$k};             # ==> 127 (obj is integer now, but...)
puts $k;               # ==> 0x7f (... still remains the string-representation)
puts [dict get $d $k]; # ==> test

# some code that fouls it up (despite of two references var `k` and key in dict `d`):
magic_happens_here $k; # string representation gets lost.

# and hereafter:
puts $k;               # ==> 127 (representation is now 127, so...)
puts [dict get $d $k]; # ==> ERROR: key "127" not known in dictionary

如您所见,重置响应。更改共享对象的字符串表示形式在设计上是错误的。
请在Tcl中避免这种情况。