From 7460f22cc7e783b1dd480c2fbf8ef6fc90a0360c Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 21 Jan 2014 21:32:53 +0000 Subject: Backport of bytearray append machinery to support bug fixes in ReadBytes. --- generic/tclBinary.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ generic/tclIO.c | 17 +++------- generic/tclInt.h | 2 ++ 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/generic/tclBinary.c b/generic/tclBinary.c index dbb296b..68289f2 100644 --- a/generic/tclBinary.c +++ b/generic/tclBinary.c @@ -549,6 +549,98 @@ UpdateStringOfByteArray( /* *---------------------------------------------------------------------- * + * TclAppendBytesToByteArray -- + * + * This function appends an array of bytes to a byte array object. Note + * that the object *must* be unshared, and the array of bytes *must not* + * refer to the object being appended to. + * + * Results: + * None. + * + * Side effects: + * Allocates enough memory for an array of bytes of the requested total + * size, or possibly larger. [Bug 2992970] + * + *---------------------------------------------------------------------- + */ + +#define TCL_MIN_GROWTH 1024 +void +TclAppendBytesToByteArray( + Tcl_Obj *objPtr, + const unsigned char *bytes, + int len) +{ + ByteArray *byteArrayPtr; + int needed; + + if (Tcl_IsShared(objPtr)) { + Tcl_Panic("%s called with shared object","TclAppendBytesToByteArray"); + } + if (len < 0) { + Tcl_Panic("%s must be called with definite number of bytes to append", + "TclAppendBytesToByteArray"); + } + if (len == 0) { + /* Append zero bytes is a no-op. */ + return; + } + if (objPtr->typePtr != &tclByteArrayType) { + SetByteArrayFromAny(NULL, objPtr); + } + byteArrayPtr = GET_BYTEARRAY(objPtr); + + if (len > INT_MAX - byteArrayPtr->used) { + Tcl_Panic("max size for a Tcl value (%d bytes) exceeded", INT_MAX); + } + + needed = byteArrayPtr->used + len; + /* + * If we need to, resize the allocated space in the byte array. + */ + + if (needed > byteArrayPtr->allocated) { + ByteArray *ptr = NULL; + int attempt; + + if (needed <= INT_MAX/2) { + /* Try to allocate double the total space that is needed. */ + attempt = 2 * needed; + ptr = (ByteArray *) attemptckrealloc((void *) byteArrayPtr, + BYTEARRAY_SIZE(attempt)); + } + if (ptr == NULL) { + /* Try to allocate double the increment that is needed (plus). */ + unsigned int limit = INT_MAX - needed; + unsigned int extra = len + TCL_MIN_GROWTH; + int growth = (int) ((extra > limit) ? limit : extra); + + attempt = needed + growth; + ptr = (ByteArray *) attemptckrealloc((void *) byteArrayPtr, + BYTEARRAY_SIZE(attempt)); + } + if (ptr == NULL) { + /* Last chance: Try to allocate exactly what is needed. */ + attempt = needed; + ptr = (ByteArray *) ckrealloc((void *)byteArrayPtr, + BYTEARRAY_SIZE(attempt)); + } + byteArrayPtr = ptr; + byteArrayPtr->allocated = attempt; + SET_BYTEARRAY(objPtr, byteArrayPtr); + } + + if (bytes) { + memcpy(byteArrayPtr->bytes + byteArrayPtr->used, bytes, len); + } + byteArrayPtr->used += len; + TclInvalidateStringRep(objPtr); +} + +/* + *---------------------------------------------------------------------- + * * Tcl_BinaryObjCmd -- * * This procedure implements the "binary" Tcl command. diff --git a/generic/tclIO.c b/generic/tclIO.c index f1d8909..c1b7ee9 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5586,20 +5586,8 @@ ReadBytes( toRead = srcLen; } + TclAppendBytesToByteArray(objPtr, NULL, toRead); dst = (char *) Tcl_GetByteArrayFromObj(objPtr, &length); - if (toRead > length - offset - 1) { - /* - * Double the existing size of the object or make enough room to hold - * all the characters we may get from the source buffer, whichever is - * larger. - */ - - length = offset * 2; - if (offset < toRead) { - length = offset + toRead + 1; - } - dst = (char *) Tcl_SetByteArrayLength(objPtr, length); - } dst += offset; if (statePtr->flags & INPUT_NEED_NL) { @@ -5607,6 +5595,7 @@ ReadBytes( if ((srcLen == 0) || (*src != '\n')) { *dst = '\r'; *offsetPtr += 1; + Tcl_SetByteArrayLength(objPtr, *offsetPtr); return 1; } *dst++ = '\n'; @@ -5619,11 +5608,13 @@ ReadBytes( dstWrote = toRead; if (TranslateInputEOL(statePtr, dst, src, &dstWrote, &srcRead) != 0) { if (dstWrote == 0) { + Tcl_SetByteArrayLength(objPtr, *offsetPtr); return -1; } } bufPtr->nextRemoved += srcRead; *offsetPtr += dstWrote; + Tcl_SetByteArrayLength(objPtr, *offsetPtr); return dstWrote; } diff --git a/generic/tclInt.h b/generic/tclInt.h index dc28b97..64d39a0 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2477,6 +2477,8 @@ MODULE_SCOPE char tclEmptyString; *---------------------------------------------------------------- */ +MODULE_SCOPE void TclAppendBytesToByteArray(Tcl_Obj *objPtr, + const unsigned char *bytes, int len); MODULE_SCOPE void TclAdvanceContinuations(int* line, int** next, int loc); MODULE_SCOPE void TclAdvanceLines(int *line, const char *start, const char *end); -- cgit v0.12 From abe23bfb4ef65eb899170e5ae7c4efc030294b31 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 21 Jan 2014 22:08:16 +0000 Subject: There is no need for ReadBytes() or its caller(s) to track how many bytes are actually stored in objPtr. The ByteArray Tcl_ObjType already has the machinery to take care of this. --- generic/tclIO.c | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index c1b7ee9..4a5d8f1 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -208,7 +208,7 @@ static int HaveVersion(const Tcl_ChannelType *typePtr, static void PeekAhead(Channel *chanPtr, char **dstEndPtr, GetsState *gsPtr); static int ReadBytes(ChannelState *statePtr, Tcl_Obj *objPtr, - int charsLeft, int *offsetPtr); + int charsLeft); static int ReadChars(ChannelState *statePtr, Tcl_Obj *objPtr, int charsLeft, int *offsetPtr, int *factorPtr); static void RecycleBuffer(ChannelState *statePtr, @@ -5448,12 +5448,10 @@ DoReadChars( */ TclGetString(objPtr); + offset = 0; } - offset = 0; } else { - if (encoding == NULL) { - Tcl_GetByteArrayFromObj(objPtr, &offset); - } else { + if (encoding) { TclGetStringFromObj(objPtr, &offset); } } @@ -5462,7 +5460,7 @@ DoReadChars( copiedNow = -1; if (statePtr->inQueueHead != NULL) { if (encoding == NULL) { - copiedNow = ReadBytes(statePtr, objPtr, toRead, &offset); + copiedNow = ReadBytes(statePtr, objPtr, toRead); } else { copiedNow = ReadChars(statePtr, objPtr, toRead, &offset, &factor); @@ -5510,9 +5508,7 @@ DoReadChars( } ResetFlag(statePtr, CHANNEL_BLOCKED); - if (encoding == NULL) { - Tcl_SetByteArrayLength(objPtr, offset); - } else { + if (encoding) { Tcl_SetObjLength(objPtr, offset); } @@ -5540,13 +5536,11 @@ DoReadChars( * allocated to hold data read from the channel as needed. * * Results: - * The return value is the number of bytes appended to the object and - * *offsetPtr is filled with the total number of bytes in the object - * (greater than the return value if there were already bytes in the - * object). + * The return value is the number of bytes appended to the object, or + * -1 to indicate that zero bytes were read due to an EOF. * * Side effects: - * None. + * The storage of bytes in objPtr can cause (re-)allocation of memory. * *--------------------------------------------------------------------------- */ @@ -5559,24 +5553,18 @@ ReadBytes( * been allocated to hold data, not how many * bytes of data have been stored in the * object. */ - int bytesToRead, /* Maximum number of bytes to store, or < 0 to + int bytesToRead) /* Maximum number of bytes to store, or < 0 to * get all available bytes. Bytes are obtained * from the first buffer in the queue - even * if this number is larger than the number of * bytes available in the first buffer, only * the bytes from the first buffer are * returned. */ - int *offsetPtr) /* On input, contains how many bytes of objPtr - * have been used to hold data. On output, - * filled with how many bytes are now being - * used. */ { - int toRead, srcLen, offset, length, srcRead, dstWrote; + int toRead, srcLen, length, srcRead, dstWrote; ChannelBuffer *bufPtr; char *src, *dst; - offset = *offsetPtr; - bufPtr = statePtr->inQueueHead; src = RemovePoint(bufPtr); srcLen = BytesLeft(bufPtr); @@ -5586,16 +5574,17 @@ ReadBytes( toRead = srcLen; } + (void) Tcl_GetByteArrayFromObj(objPtr, &length); TclAppendBytesToByteArray(objPtr, NULL, toRead); - dst = (char *) Tcl_GetByteArrayFromObj(objPtr, &length); - dst += offset; + dst = (char *) Tcl_GetByteArrayFromObj(objPtr, NULL); + dst += length; if (statePtr->flags & INPUT_NEED_NL) { ResetFlag(statePtr, INPUT_NEED_NL); if ((srcLen == 0) || (*src != '\n')) { *dst = '\r'; - *offsetPtr += 1; - Tcl_SetByteArrayLength(objPtr, *offsetPtr); + length += 1; + Tcl_SetByteArrayLength(objPtr, length); return 1; } *dst++ = '\n'; @@ -5608,13 +5597,13 @@ ReadBytes( dstWrote = toRead; if (TranslateInputEOL(statePtr, dst, src, &dstWrote, &srcRead) != 0) { if (dstWrote == 0) { - Tcl_SetByteArrayLength(objPtr, *offsetPtr); + Tcl_SetByteArrayLength(objPtr, length); return -1; } } bufPtr->nextRemoved += srcRead; - *offsetPtr += dstWrote; - Tcl_SetByteArrayLength(objPtr, *offsetPtr); + length += dstWrote; + Tcl_SetByteArrayLength(objPtr, length); return dstWrote; } -- cgit v0.12 From 8703cd164100a81207d646099206dcc3acdf05bb Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 27 Jan 2014 17:35:27 +0000 Subject: Revise the Tcl_Append* machinery to tolerate NULL bytes to append. Then have ReadChars() use that machinery to resize buffer receiving input, rather than invent its own version. Simplify ReadChars() callers. --- generic/tclIO.c | 67 +++++++++++--------------------------------------- generic/tclStringObj.c | 29 ++++++++++++++-------- 2 files changed, 34 insertions(+), 62 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 972cbd8..40573d7 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -210,7 +210,7 @@ static void PeekAhead(Channel *chanPtr, char **dstEndPtr, static int ReadBytes(ChannelState *statePtr, Tcl_Obj *objPtr, int charsLeft); static int ReadChars(ChannelState *statePtr, Tcl_Obj *objPtr, - int charsLeft, int *offsetPtr, int *factorPtr); + int charsLeft, int *factorPtr); static void RecycleBuffer(ChannelState *statePtr, ChannelBuffer *bufPtr, int mustDiscard); static int StackSetBlockMode(Channel *chanPtr, int mode); @@ -5425,7 +5425,7 @@ DoReadChars( ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; - int offset, factor, copied, copiedNow, result; + int factor, copied, copiedNow, result; Tcl_Encoding encoding; #define UTF_EXPANSION_FACTOR 1024 @@ -5447,14 +5447,11 @@ DoReadChars( * We're going to access objPtr->bytes directly, so we must ensure * that this is actually a string object (otherwise it might have * been pure Unicode). + * + * Probably not needed anymore. */ TclGetString(objPtr); - offset = 0; - } - } else { - if (encoding) { - TclGetStringFromObj(objPtr, &offset); } } @@ -5464,8 +5461,7 @@ DoReadChars( if (encoding == NULL) { copiedNow = ReadBytes(statePtr, objPtr, toRead); } else { - copiedNow = ReadChars(statePtr, objPtr, toRead, &offset, - &factor); + copiedNow = ReadChars(statePtr, objPtr, toRead, &factor); } /* @@ -5510,9 +5506,6 @@ DoReadChars( } ResetFlag(statePtr, CHANNEL_BLOCKED); - if (encoding) { - Tcl_SetObjLength(objPtr, offset); - } /* * Update the notifier state so we don't block while there is still data @@ -5651,17 +5644,13 @@ ReadChars( * available in the first buffer, only the * characters from the first buffer are * returned. */ - int *offsetPtr, /* On input, contains how many bytes of objPtr - * have been used to hold data. On output, - * filled with how many bytes are now being - * used. */ int *factorPtr) /* On input, contains a guess of how many * bytes need to be allocated to hold the * result of converting N source bytes to * UTF-8. On output, contains another guess * based on the data seen so far. */ { - int toRead, factor, offset, spaceLeft, srcLen, dstNeeded; + int toRead, factor, srcLen, dstNeeded, numBytes; int srcRead, dstWrote, numChars, dstRead; ChannelBuffer *bufPtr; char *src, *dst; @@ -5669,7 +5658,6 @@ ReadChars( int encEndFlagSuppressed = 0; factor = *factorPtr; - offset = *offsetPtr; bufPtr = statePtr->inQueueHead; src = RemovePoint(bufPtr); @@ -5687,37 +5675,9 @@ ReadChars( */ dstNeeded = TCL_UTF_MAX - 1 + toRead * factor / UTF_EXPANSION_FACTOR; - spaceLeft = objPtr->length - offset; - - if (dstNeeded > spaceLeft) { - /* - * Double the existing size of the object or make enough room to hold - * all the characters we want from the source buffer, whichever is - * larger. - */ - - int length = offset + ((offset < dstNeeded) ? dstNeeded : offset); - - if (Tcl_AttemptSetObjLength(objPtr, length) == 0) { - length = offset + dstNeeded; - if (Tcl_AttemptSetObjLength(objPtr, length) == 0) { - dstNeeded = TCL_UTF_MAX - 1 + toRead; - length = offset + dstNeeded; - Tcl_SetObjLength(objPtr, length); - } - } - spaceLeft = length - offset; - } - if (toRead == srcLen) { - /* - * Want to convert the whole buffer in one pass. If we have enough - * space, convert it using all available space in object rather than - * using the factor. - */ - - dstNeeded = spaceLeft; - } - dst = objPtr->bytes + offset; + (void) TclGetStringFromObj(objPtr, &numBytes); + Tcl_AppendToObj(objPtr, NULL, dstNeeded); + dst = TclGetString(objPtr) + numBytes; /* * [Bug 1462248]: The cause of the crash reported in this bug is this: @@ -5788,7 +5748,7 @@ ReadChars( *dst = '\r'; } statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; - *offsetPtr += 1; + Tcl_SetObjLength(objPtr, numBytes + 1); if (encEndFlagSuppressed) { statePtr->inputEncodingFlags |= TCL_ENCODING_END; @@ -5829,6 +5789,7 @@ ReadChars( SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); } + Tcl_SetObjLength(objPtr, numBytes); return -1; } @@ -5853,7 +5814,8 @@ ReadChars( memcpy(RemovePoint(nextPtr), src, (size_t) srcLen); RecycleBuffer(statePtr, bufPtr, 0); statePtr->inQueueHead = nextPtr; - return ReadChars(statePtr, objPtr, charsToRead, offsetPtr, factorPtr); + Tcl_SetObjLength(objPtr, numBytes); + return ReadChars(statePtr, objPtr, charsToRead, factorPtr); } dstRead = dstWrote; @@ -5866,6 +5828,7 @@ ReadChars( */ if (dstWrote == 0) { + Tcl_SetObjLength(objPtr, numBytes); return -1; } statePtr->inputEncodingState = oldState; @@ -5905,7 +5868,7 @@ ReadChars( if (dstWrote > srcRead + 1) { *factorPtr = dstWrote * UTF_EXPANSION_FACTOR / srcRead; } - *offsetPtr += dstWrote; + Tcl_SetObjLength(objPtr, numBytes + dstWrote); return numChars; } diff --git a/generic/tclStringObj.c b/generic/tclStringObj.c index a929d04..d96d814 100644 --- a/generic/tclStringObj.c +++ b/generic/tclStringObj.c @@ -1139,7 +1139,8 @@ Tcl_AppendLimitedToObj( if (ellipsis == NULL) { ellipsis = "..."; } - toCopy = Tcl_UtfPrev(bytes+limit+1-strlen(ellipsis), bytes) - bytes; + toCopy = (bytes == NULL) ? limit + : Tcl_UtfPrev(bytes+limit+1-strlen(ellipsis), bytes) - bytes; } /* @@ -1386,7 +1387,8 @@ AppendUnicodeToUnicodeRep( * due to the reallocs below. */ int offset = -1; - if (unicode >= stringPtr->unicode && unicode <= stringPtr->unicode + if (unicode && unicode >= stringPtr->unicode + && unicode <= stringPtr->unicode + stringPtr->uallocated / sizeof(Tcl_UniChar)) { offset = unicode - stringPtr->unicode; } @@ -1405,8 +1407,10 @@ AppendUnicodeToUnicodeRep( * trailing null. */ - memcpy(stringPtr->unicode + stringPtr->numChars, unicode, - appendNumChars * sizeof(Tcl_UniChar)); + if (unicode) { + memcpy(stringPtr->unicode + stringPtr->numChars, unicode, + appendNumChars * sizeof(Tcl_UniChar)); + } stringPtr->unicode[numChars] = 0; stringPtr->numChars = numChars; stringPtr->allocated = 0; @@ -1478,8 +1482,8 @@ AppendUtfToUnicodeRep( int numBytes) /* Number of bytes of "bytes" to convert. */ { Tcl_DString dsPtr; - int numChars; - Tcl_UniChar *unicode; + int numChars = numBytes; + Tcl_UniChar *unicode = NULL; if (numBytes < 0) { numBytes = (bytes ? strlen(bytes) : 0); @@ -1489,8 +1493,11 @@ AppendUtfToUnicodeRep( } Tcl_DStringInit(&dsPtr); - numChars = Tcl_NumUtfChars(bytes, numBytes); - unicode = (Tcl_UniChar *)Tcl_UtfToUniCharDString(bytes, numBytes, &dsPtr); + if (bytes) { + numChars = Tcl_NumUtfChars(bytes, numBytes); + unicode = (Tcl_UniChar *) Tcl_UtfToUniCharDString(bytes, numBytes, + &dsPtr); + } AppendUnicodeToUnicodeRep(objPtr, unicode, numChars); Tcl_DStringFree(&dsPtr); } @@ -1547,7 +1554,7 @@ AppendUtfToUtfRep( * due to the reallocs below. */ int offset = -1; - if (bytes >= objPtr->bytes + if (bytes && bytes >= objPtr->bytes && bytes <= objPtr->bytes + objPtr->length) { offset = bytes - objPtr->bytes; } @@ -1585,7 +1592,9 @@ AppendUtfToUtfRep( stringPtr->numChars = -1; stringPtr->hasUnicode = 0; - memcpy(objPtr->bytes + oldLength, bytes, (size_t) numBytes); + if (bytes) { + memcpy(objPtr->bytes + oldLength, bytes, (size_t) numBytes); + } objPtr->bytes[newLength] = 0; objPtr->length = newLength; } -- cgit v0.12 From 11b2556b272a74d9456a2b0b9cef5ccc76fd8316 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 6 Feb 2014 19:04:36 +0000 Subject: Revised ReadChars to restore an attempt to make sure we do not short read because of a false notion of limited storage space. The test suite does not appear to demonstrate any case where this matters. Could be an incomplete test suite, or an example of pointless code. --- generic/tclIO.c | 8 +++++++- generic/tclInt.h | 2 ++ generic/tclStringObj.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index f8baba3..cedf3f6 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5310,7 +5310,13 @@ ReadChars( dstNeeded = TCL_UTF_MAX - 1 + toRead * factor / UTF_EXPANSION_FACTOR; (void) TclGetStringFromObj(objPtr, &numBytes); Tcl_AppendToObj(objPtr, NULL, dstNeeded); - dst = TclGetString(objPtr) + numBytes; + if (toRead == srcLen) { + unsigned int size; + dst = TclGetStringStorage(objPtr, &size) + numBytes; + dstNeeded = size - numBytes; + } else { + dst = TclGetString(objPtr) + numBytes; + } /* * [Bug 1462248]: The cause of the crash reported in this bug is this: diff --git a/generic/tclInt.h b/generic/tclInt.h index a998460..0c09ec0 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2571,6 +2571,8 @@ MODULE_SCOPE int TclGetOpenModeEx(Tcl_Interp *interp, int *binaryPtr); MODULE_SCOPE Tcl_Obj * TclGetProcessGlobalValue(ProcessGlobalValue *pgvPtr); MODULE_SCOPE const char *TclGetSrcInfoForCmd(Interp *iPtr, int *lenPtr); +MODULE_SCOPE char * TclGetStringStorage(Tcl_Obj *objPtr, + unsigned int *sizePtr); MODULE_SCOPE int TclGlob(Tcl_Interp *interp, char *pattern, Tcl_Obj *unquotedPrefix, int globFlags, Tcl_GlobTypeData *types); diff --git a/generic/tclStringObj.c b/generic/tclStringObj.c index d96d814..8c6a376 100644 --- a/generic/tclStringObj.c +++ b/generic/tclStringObj.c @@ -2711,6 +2711,38 @@ Tcl_ObjPrintf( /* *--------------------------------------------------------------------------- * + * TclGetStringStorage -- + * + * Returns the string storage space of a Tcl_Obj. + * + * Results: + * The pointer value objPtr->bytes is returned and the number of bytes + * allocated there is written to *sizePtr (if known). + * + * Side effects: + * May set objPtr->bytes. + * + *--------------------------------------------------------------------------- + */ + +char * +TclGetStringStorage( + Tcl_Obj *objPtr, + unsigned int *sizePtr) +{ + String *stringPtr; + + if (objPtr->typePtr != &tclStringType || objPtr->bytes == NULL) { + return TclGetStringFromObj(objPtr, (int *)sizePtr); + } + + stringPtr = GET_STRING(objPtr); + *sizePtr = stringPtr->allocated; + return objPtr->bytes; +} +/* + *--------------------------------------------------------------------------- + * * TclStringObjReverse -- * * Implements the [string reverse] operation. -- cgit v0.12 From 08700ad2348944e47107ddbcbf18bbd7d861668d Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 11 Feb 2014 19:53:41 +0000 Subject: Refactor so that CopyAndTranslateBuffer() calls on TranslateInputEOL() instead of duplicating so much of its function. Note the testing gaps. --- generic/tclIO.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index cedf3f6..09b8191 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -8805,8 +8805,6 @@ CopyAndTranslateBuffer( * in the current input buffer? */ int copied; /* How many characters were already copied * into the destination space? */ - int i; /* Iterates over the copied input looking for - * the input eofChar. */ /* * If there is no input at all, return zero. The invariant is that either @@ -8821,6 +8819,15 @@ CopyAndTranslateBuffer( bufPtr = statePtr->inQueueHead; bytesInBuffer = BytesLeft(bufPtr); +#if 1 + copied = space; + if (bytesInBuffer <= copied) { + copied = bytesInBuffer; + } + TranslateInputEOL(statePtr, result, RemovePoint(bufPtr), + &copied, &bytesInBuffer); + bufPtr->nextRemoved += copied; +#else copied = 0; switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: @@ -8842,6 +8849,7 @@ CopyAndTranslateBuffer( case TCL_TRANSLATE_CR: { char *end; + Tcl_Panic("Untested"); if (bytesInBuffer == 0) { return 0; } @@ -8873,6 +8881,7 @@ CopyAndTranslateBuffer( * If there is a held-back "\r" at EOF, produce it now. */ + Tcl_Panic("Untested"); if (bytesInBuffer == 0) { if ((statePtr->flags & (INPUT_SAW_CR | CHANNEL_EOF)) == (INPUT_SAW_CR | CHANNEL_EOF)) { @@ -8940,6 +8949,7 @@ CopyAndTranslateBuffer( for (src = result; src < end; src++) { curByte = *src; if (curByte == '\r') { + Tcl_Panic("Untested"); SetFlag(statePtr, INPUT_SAW_CR); *dst = '\n'; dst++; @@ -8965,6 +8975,9 @@ CopyAndTranslateBuffer( */ if (statePtr->inEofChar != 0) { + int i; + + Tcl_Panic("Untested"); for (i = 0; i < copied; i++) { if (result[i] == (char) statePtr->inEofChar) { /* @@ -8979,6 +8992,7 @@ CopyAndTranslateBuffer( } } } +#endif /* * If the current buffer is empty recycle it. -- cgit v0.12 From abe90f6c1b82d92b9e000f861edde447cf1d7863 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 18 Feb 2014 17:54:06 +0000 Subject: Coverage test for -translation auto handling of INPUT_SAW_CR flag. Demonstrates refactor failure. --- generic/tclIO.c | 1 - tests/io.test | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 09b8191..20101c2 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -8949,7 +8949,6 @@ CopyAndTranslateBuffer( for (src = result; src < end; src++) { curByte = *src; if (curByte == '\r') { - Tcl_Panic("Untested"); SetFlag(statePtr, INPUT_SAW_CR); *dst = '\n'; dst++; diff --git a/tests/io.test b/tests/io.test index 68051d7..e08c57a 100644 --- a/tests/io.test +++ b/tests/io.test @@ -6730,6 +6730,21 @@ test io-52.11 {TclCopyChannel & encodings} {fcopy} { file size $path(kyrillic.txt) } 3 +test io-52.12 {coverage of -translation auto} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -buffersize 8 + set out [open $path(test2) w] + fcopy $in $out + close $in + close $out + file size $path(test2) +} 29 + test io-53.1 {CopyData} {fcopy} { file delete $path(test1) set f1 [open $thisScript] -- cgit v0.12 From e3b160fb968cfca1ba3255292e5583bd0bf3e37d Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 18 Feb 2014 18:26:22 +0000 Subject: Refactor correction exposed by coverage test. --- generic/tclIO.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 20101c2..01af6dc 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -8826,7 +8826,7 @@ CopyAndTranslateBuffer( } TranslateInputEOL(statePtr, result, RemovePoint(bufPtr), &copied, &bytesInBuffer); - bufPtr->nextRemoved += copied; + bufPtr->nextRemoved += bytesInBuffer; #else copied = 0; switch (statePtr->inputTranslation) { -- cgit v0.12 From 997ad71bccf25cf78178d99b5bd94103ef365e4d Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 18 Feb 2014 18:35:19 +0000 Subject: coverage test for -translation cr --- generic/tclIO.c | 1 - tests/io.test | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 01af6dc..4197dc0 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -8849,7 +8849,6 @@ CopyAndTranslateBuffer( case TCL_TRANSLATE_CR: { char *end; - Tcl_Panic("Untested"); if (bytesInBuffer == 0) { return 0; } diff --git a/tests/io.test b/tests/io.test index e08c57a..0c2944b 100644 --- a/tests/io.test +++ b/tests/io.test @@ -6744,6 +6744,20 @@ test io-52.12 {coverage of -translation auto} { close $out file size $path(test2) } 29 +test io-52.13 {coverage of -translation cr} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -buffersize 8 -translation cr + set out [open $path(test2) w] + fcopy $in $out + close $in + close $out + file size $path(test2) +} 30 test io-53.1 {CopyData} {fcopy} { file delete $path(test1) -- cgit v0.12 From 9afa8a13e86fbd71a030ead7909cbe7d7db76296 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 18 Feb 2014 21:27:35 +0000 Subject: Another coverage test that reveals refactoring error. --- generic/tclIO.c | 2 +- tests/io.test | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 4197dc0..c862923 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -8880,10 +8880,10 @@ CopyAndTranslateBuffer( * If there is a held-back "\r" at EOF, produce it now. */ - Tcl_Panic("Untested"); if (bytesInBuffer == 0) { if ((statePtr->flags & (INPUT_SAW_CR | CHANNEL_EOF)) == (INPUT_SAW_CR | CHANNEL_EOF)) { + Tcl_Panic("Untested"); result[0] = '\r'; ResetFlag(statePtr, INPUT_SAW_CR); return 1; diff --git a/tests/io.test b/tests/io.test index 0c2944b..4df44a3 100644 --- a/tests/io.test +++ b/tests/io.test @@ -6758,6 +6758,20 @@ test io-52.13 {coverage of -translation cr} { close $out file size $path(test2) } 30 +test io-52.14 {coverage of -translation crlf} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -buffersize 8 -translation crlf + set out [open $path(test2) w] + fcopy $in $out + close $in + close $out + file size $path(test2) +} 29 test io-53.1 {CopyData} {fcopy} { file delete $path(test1) -- cgit v0.12 From 1a35a544342c26a5fa207edcd05448d6f525d9a1 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 18 Feb 2014 23:20:06 +0000 Subject: Callers of TranslateInputEOL are expected to manage the INPUT_NEED_NL flag. --- generic/tclIO.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index c862923..68d370a 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -8824,6 +8824,20 @@ CopyAndTranslateBuffer( if (bytesInBuffer <= copied) { copied = bytesInBuffer; } + if (copied == 0) { + return 0; + } + if (statePtr->flags & INPUT_NEED_NL) { + ResetFlag(statePtr, INPUT_NEED_NL); + + if (RemovePoint(bufPtr)[0] == '\n') { + bufPtr->nextRemoved++; + *result = '\n'; + } else { + *result = '\r'; + } + return 1; + } TranslateInputEOL(statePtr, result, RemovePoint(bufPtr), &copied, &bytesInBuffer); bufPtr->nextRemoved += bytesInBuffer; -- cgit v0.12 From 03c5b98228950023fde73077aa1b3e401e373d1c Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 19 Feb 2014 03:36:15 +0000 Subject: Shortcut ReadBytes() when it's a no-op. --- generic/tclIO.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 68d370a..7820242 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5201,6 +5201,9 @@ ReadBytes( if ((unsigned) toRead > (unsigned) srcLen) { toRead = srcLen; } + if (toRead == 0) { + return 0; + } (void) Tcl_GetByteArrayFromObj(objPtr, &length); TclAppendBytesToByteArray(objPtr, NULL, toRead); @@ -5209,7 +5212,7 @@ ReadBytes( if (statePtr->flags & INPUT_NEED_NL) { ResetFlag(statePtr, INPUT_NEED_NL); - if ((srcLen == 0) || (*src != '\n')) { + if (*src != '\n') { *dst = '\r'; length += 1; Tcl_SetByteArrayLength(objPtr, length); -- cgit v0.12 From e9a5cb0bbfec8877ccb5b56d39e6100ba9c5e42d Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 19 Feb 2014 03:45:17 +0000 Subject: Next coverage test to expose another refactoring error. --- generic/tclIO.c | 1 - tests/io.test | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 7820242..3b2b53e 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -8900,7 +8900,6 @@ CopyAndTranslateBuffer( if (bytesInBuffer == 0) { if ((statePtr->flags & (INPUT_SAW_CR | CHANNEL_EOF)) == (INPUT_SAW_CR | CHANNEL_EOF)) { - Tcl_Panic("Untested"); result[0] = '\r'; ResetFlag(statePtr, INPUT_SAW_CR); return 1; diff --git a/tests/io.test b/tests/io.test index 4df44a3..8c066ca 100644 --- a/tests/io.test +++ b/tests/io.test @@ -6772,6 +6772,20 @@ test io-52.14 {coverage of -translation crlf} { close $out file size $path(test2) } 29 +test io-52.15 {coverage of -translation crlf} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\r + close $out + set in [open $path(test1)] + chan configure $in -buffersize 8 -translation crlf + set out [open $path(test2) w] + fcopy $in $out + close $in + close $out + file size $path(test2) +} 8 test io-53.1 {CopyData} {fcopy} { file delete $path(test1) -- cgit v0.12 From d0f15c03d3f5385a24eea5b7b2cdc6f8d95a8933 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 20 Feb 2014 03:51:16 +0000 Subject: Refactoring repair to fix failing test. --- generic/tclIO.c | 52 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 3b2b53e..3cddc29 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -8808,6 +8808,7 @@ CopyAndTranslateBuffer( * in the current input buffer? */ int copied; /* How many characters were already copied * into the destination space? */ + int toCopy; /* * If there is no input at all, return zero. The invariant is that either @@ -8822,30 +8823,51 @@ CopyAndTranslateBuffer( bufPtr = statePtr->inQueueHead; bytesInBuffer = BytesLeft(bufPtr); + copied = 0; #if 1 - copied = space; - if (bytesInBuffer <= copied) { - copied = bytesInBuffer; - } - if (copied == 0) { - return 0; - } if (statePtr->flags & INPUT_NEED_NL) { - ResetFlag(statePtr, INPUT_NEED_NL); - if (RemovePoint(bufPtr)[0] == '\n') { - bufPtr->nextRemoved++; - *result = '\n'; - } else { + /* + * An earlier call to TranslateInputEOL ended in the read of a \r . + * Only the next read from the same channel can complete the + * translation sequence to tell us what character we should read. + */ + + if (bytesInBuffer) { + /* There's a next byte. It will settle things. */ + ResetFlag(statePtr, INPUT_NEED_NL); + + if (RemovePoint(bufPtr)[0] == '\n') { + bufPtr->nextRemoved++; + bytesInBuffer--; + *result++ = '\n'; + } else { + *result++ = '\r'; + } + copied++; + space--; + } else if (statePtr->flags & CHANNEL_EOF) { + /* There is no next byte, and there never will be (EOF). */ + ResetFlag(statePtr, INPUT_NEED_NL); *result = '\r'; + return 1; + } else { + /* There is no next byte. Ask the caller to read more. */ + return 0; } - return 1; + } + toCopy = space; + if (bytesInBuffer <= toCopy) { + toCopy = bytesInBuffer; + } + if (toCopy == 0) { + return copied; } TranslateInputEOL(statePtr, result, RemovePoint(bufPtr), - &copied, &bytesInBuffer); + &toCopy, &bytesInBuffer); bufPtr->nextRemoved += bytesInBuffer; + copied += toCopy; #else - copied = 0; switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: if (bytesInBuffer == 0) { -- cgit v0.12 From ddaf1d27cb4ec6db78294cb42e1fd46ae6d2dbc2 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 20 Feb 2014 20:31:55 +0000 Subject: Can we send some binary reads down the char-reading path? --- generic/tclIO.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 3636861..4d7133a 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5096,7 +5096,8 @@ DoReadChars( for (copied = 0; (unsigned) toRead > 0; ) { copiedNow = -1; if (statePtr->inQueueHead != NULL) { - if (encoding == NULL) { + if (encoding == NULL + && statePtr->inputTranslation == TCL_TRANSLATE_LF) { copiedNow = ReadBytes(statePtr, objPtr, toRead, &offset); } else { copiedNow = ReadChars(statePtr, objPtr, toRead, &offset, @@ -5320,6 +5321,8 @@ ReadChars( char *src, *dst; Tcl_EncodingState oldState; int encEndFlagSuppressed = 0; + Tcl_Encoding encoding = statePtr->encoding? statePtr->encoding + : GetBinaryEncoding(); factor = *factorPtr; offset = *offsetPtr; @@ -5424,7 +5427,7 @@ ReadChars( */ ResetFlag(statePtr, INPUT_NEED_NL); - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, + Tcl_ExternalToUtf(NULL, encoding, src, srcLen, statePtr->inputEncodingFlags, &statePtr->inputEncodingState, dst, TCL_UTF_MAX + 1, &srcRead, &dstWrote, &numChars); if ((dstWrote > 0) && (*dst == '\n')) { @@ -5449,7 +5452,7 @@ ReadChars( return 1; } - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, + Tcl_ExternalToUtf(NULL, encoding, src, srcLen, statePtr->inputEncodingFlags, &statePtr->inputEncodingState, dst, dstNeeded + 1, &srcRead, &dstWrote, &numChars); @@ -5522,7 +5525,7 @@ ReadChars( return -1; } statePtr->inputEncodingState = oldState; - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, + Tcl_ExternalToUtf(NULL, encoding, src, srcLen, statePtr->inputEncodingFlags, &statePtr->inputEncodingState, dst, dstRead + TCL_UTF_MAX, &srcRead, &dstWrote, &numChars); TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); @@ -5545,7 +5548,7 @@ ReadChars( eof = Tcl_UtfAtIndex(dst, toRead); statePtr->inputEncodingState = oldState; - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, + Tcl_ExternalToUtf(NULL, encoding, src, srcLen, statePtr->inputEncodingFlags, &statePtr->inputEncodingState, dst, eof - dst + TCL_UTF_MAX, &srcRead, &dstWrote, &numChars); dstRead = dstWrote; -- cgit v0.12 From 9f6aaa68fc35449d224e5a1ea5d53e09ac38e509 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 20 Feb 2014 21:23:33 +0000 Subject: Switch consistently on the narrower def of binary mode. --- generic/tclIO.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 4d7133a..eae063b 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5060,6 +5060,7 @@ DoReadChars( ChannelBuffer *bufPtr; int offset, factor, copied, copiedNow, result; Tcl_Encoding encoding; + int binaryMode; #define UTF_EXPANSION_FACTOR 1024 /* @@ -5070,8 +5071,12 @@ DoReadChars( encoding = statePtr->encoding; factor = UTF_EXPANSION_FACTOR; + binaryMode = (encoding == NULL) + && (statePtr->inputTranslation == TCL_TRANSLATE_LF) + && (statePtr->inEofChar == NULL); + if (appendFlag == 0) { - if (encoding == NULL) { + if (binaryMode) { Tcl_SetByteArrayLength(objPtr, 0); } else { Tcl_SetObjLength(objPtr, 0); @@ -5086,7 +5091,7 @@ DoReadChars( } offset = 0; } else { - if (encoding == NULL) { + if (binaryMode) { Tcl_GetByteArrayFromObj(objPtr, &offset); } else { TclGetStringFromObj(objPtr, &offset); @@ -5096,8 +5101,7 @@ DoReadChars( for (copied = 0; (unsigned) toRead > 0; ) { copiedNow = -1; if (statePtr->inQueueHead != NULL) { - if (encoding == NULL - && statePtr->inputTranslation == TCL_TRANSLATE_LF) { + if (binaryMode) { copiedNow = ReadBytes(statePtr, objPtr, toRead, &offset); } else { copiedNow = ReadChars(statePtr, objPtr, toRead, &offset, @@ -5146,7 +5150,7 @@ DoReadChars( } ResetFlag(statePtr, CHANNEL_BLOCKED); - if (encoding == NULL) { + if (binaryMode) { Tcl_SetByteArrayLength(objPtr, offset); } else { Tcl_SetObjLength(objPtr, offset); -- cgit v0.12 From 82eaf13ae6136c9679b5aeba5c75cd777f2829dd Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Feb 2014 15:02:35 +0000 Subject: fix type error --- generic/tclIO.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index eae063b..ac28ec0 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5073,7 +5073,7 @@ DoReadChars( binaryMode = (encoding == NULL) && (statePtr->inputTranslation == TCL_TRANSLATE_LF) - && (statePtr->inEofChar == NULL); + && (statePtr->inEofChar == '\0'); if (appendFlag == 0) { if (binaryMode) { -- cgit v0.12 From 527d583d939f70450bc8b3db5077dd7d806c7c3e Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Feb 2014 15:12:16 +0000 Subject: Simplify ReadBytes based on new constraints. --- generic/tclIO.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index ac28ec0..23e1fbf 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5242,6 +5242,10 @@ ReadBytes( } dst += offset; +#if 1 + memcpy(dst, src, (size_t) toRead); + srcRead = dstWrote = toRead; +#else if (statePtr->flags & INPUT_NEED_NL) { ResetFlag(statePtr, INPUT_NEED_NL); if ((srcLen == 0) || (*src != '\n')) { @@ -5262,6 +5266,7 @@ ReadBytes( return -1; } } +#endif bufPtr->nextRemoved += srcRead; *offsetPtr += dstWrote; return dstWrote; -- cgit v0.12 From 73bf5da01200e7f7127273188ea24d751eb75ddf Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 24 Feb 2014 21:01:07 +0000 Subject: simplification trims --- generic/tclIO.c | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 5625ff2..1c5fed4 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5194,7 +5194,7 @@ ReadBytes( * the bytes from the first buffer are * returned. */ { - int toRead, srcLen, length, srcRead, dstWrote; + int toRead, srcLen, length; ChannelBuffer *bufPtr; char *src, *dst; @@ -5215,37 +5215,11 @@ ReadBytes( dst = (char *) Tcl_GetByteArrayFromObj(objPtr, NULL); dst += length; -#if 1 memcpy(dst, src, (size_t) toRead); - srcRead = dstWrote = toRead; -#else - if (statePtr->flags & INPUT_NEED_NL) { - ResetFlag(statePtr, INPUT_NEED_NL); - if (*src != '\n') { - *dst = '\r'; - length += 1; - Tcl_SetByteArrayLength(objPtr, length); - return 1; - } - *dst++ = '\n'; - src++; - srcLen--; - toRead--; - } - - srcRead = srcLen; - dstWrote = toRead; - if (TranslateInputEOL(statePtr, dst, src, &dstWrote, &srcRead) != 0) { - if (dstWrote == 0) { - Tcl_SetByteArrayLength(objPtr, length); - return -1; - } - } -#endif - bufPtr->nextRemoved += srcRead; - length += dstWrote; + bufPtr->nextRemoved += toRead; + length += toRead; Tcl_SetByteArrayLength(objPtr, length); - return dstWrote; + return toRead; } /* -- cgit v0.12 From c7f19f76c5362c2918fe01d49808b3246fd84100 Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 24 Feb 2014 21:25:08 +0000 Subject: Reduce ReadBytes to simplest expression. --- generic/tclIO.c | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 1c5fed4..a73f041 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5194,31 +5194,13 @@ ReadBytes( * the bytes from the first buffer are * returned. */ { - int toRead, srcLen, length; - ChannelBuffer *bufPtr; - char *src, *dst; - - bufPtr = statePtr->inQueueHead; - src = RemovePoint(bufPtr); - srcLen = BytesLeft(bufPtr); - - toRead = bytesToRead; - if ((unsigned) toRead > (unsigned) srcLen) { - toRead = srcLen; - } - if (toRead == 0) { - return 0; - } - - (void) Tcl_GetByteArrayFromObj(objPtr, &length); - TclAppendBytesToByteArray(objPtr, NULL, toRead); - dst = (char *) Tcl_GetByteArrayFromObj(objPtr, NULL); - dst += length; + ChannelBuffer *bufPtr = statePtr->inQueueHead; + int srcLen = BytesLeft(bufPtr); + int toRead = bytesToRead>srcLen || bytesToRead<0 ? srcLen : bytesToRead; - memcpy(dst, src, (size_t) toRead); + TclAppendBytesToByteArray(objPtr, (unsigned char *) RemovePoint(bufPtr), + toRead); bufPtr->nextRemoved += toRead; - length += toRead; - Tcl_SetByteArrayLength(objPtr, length); return toRead; } -- cgit v0.12 From e3adf1d9a076bcb2704e4364c50097b49e6348c5 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 26 Feb 2014 11:26:00 +0000 Subject: More coverage tests and bug fixes. --- generic/tclIO.c | 3 +-- tests/io.test | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index a73f041..c2a8cab 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5644,7 +5644,7 @@ TranslateInputEOL( SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; - ResetFlag(statePtr, INPUT_SAW_CR | INPUT_NEED_NL); +// ResetFlag(statePtr, INPUT_SAW_CR | INPUT_NEED_NL); return 1; } @@ -8981,7 +8981,6 @@ CopyAndTranslateBuffer( if (statePtr->inEofChar != 0) { int i; - Tcl_Panic("Untested"); for (i = 0; i < copied; i++) { if (result[i] == (char) statePtr->inEofChar) { /* diff --git a/tests/io.test b/tests/io.test index 8c066ca..6f4877f 100644 --- a/tests/io.test +++ b/tests/io.test @@ -6786,6 +6786,62 @@ test io-52.15 {coverage of -translation crlf} { close $out file size $path(test2) } 8 +test io-52.16 {coverage of eofChar handling} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -buffersize 8 -translation lf -eofchar a + set out [open $path(test2) w] + fcopy $in $out + close $in + close $out + file size $path(test2) +} 0 +test io-52.17 {coverage of eofChar handling} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -buffersize 8 -translation lf -eofchar d + set out [open $path(test2) w] + fcopy $in $out + close $in + close $out + file size $path(test2) +} 3 +test io-52.18 {coverage of eofChar handling} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -buffersize 8 -translation crlf -eofchar h + set out [open $path(test2) w] + fcopy $in $out + close $in + close $out + file size $path(test2) +} 8 +test io-52.19 {coverage of eofChar handling} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -buffersize 10 -translation crlf -eofchar h + set out [open $path(test2) w] + fcopy $in $out + close $in + close $out + file size $path(test2) +} 8 test io-53.1 {CopyData} {fcopy} { file delete $path(test1) -- cgit v0.12 From aad7393c9adb8f82f2594929954960b91d027032 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 27 Feb 2014 20:11:47 +0000 Subject: remove comment --- generic/tclIO.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index c2a8cab..8d75bf2 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5644,7 +5644,7 @@ TranslateInputEOL( SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; -// ResetFlag(statePtr, INPUT_SAW_CR | INPUT_NEED_NL); + ResetFlag(statePtr, INPUT_SAW_CR | INPUT_NEED_NL); return 1; } -- cgit v0.12 From 3df8548690a047e4fa9a445a253636a3e3a652df Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 27 Feb 2014 20:21:10 +0000 Subject: Work in progress attempting a ReadChars rewrite. --- generic/tclIO.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 3636861..20428b5 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5372,6 +5372,120 @@ ReadChars( } dst = objPtr->bytes + offset; +#if 1 + + /* + * This routine is burdened with satisfying several constraints. + * It cannot append more than 'charsToRead` chars onto objPtr. + * This is measured after encoding and translation transformations + * are completed. There is no precise number of src bytes that can + * be associated with the limit. Yet, when we are done, we must know + * precisely the number of src bytes that were consumed to produce + * the appended chars, so that all subsequent bytes are left in + * the buffers for future read operations. + * + * The consequence is that we have no choice but to implement a + * "trial and error" approach, where in general we may need to + * perform transformations and copies multiple times to achieve + * a consistent set of results. This takes the shape of a loop. + */ + + int dstLimit = dstNeeded + 1; + int savedFlags = statePtr->flags; + int savedIEFlags = statePtr->inputEncodingFlags; + Tcl_EncodingState savedState = statePtr->inputEncodingState; + + while (1) { + int dstDecoded; + + /* + * Perform the encoding transformation. Read no more than + * srcLen bytes, write no more than dstLimit bytes. + */ + +//fprintf(stdout, "Start %d %d\n", dstLimit, srcLen); fflush(stdout); + int code = Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, + statePtr->inputEncodingFlags & (bufPtr->nextPtr + ? ~0 : ~TCL_ENCODING_END), &statePtr->inputEncodingState, + dst, dstLimit, &srcRead, &dstDecoded, &numChars); + + /* + * Perform the translation transformation in place. Read no more + * than the dstDecoded bytes the encoding transformation actually + * produced. Capture the number of bytes written in dstWrote. + * Capture the number of bytes actually consumed in dstRead. + */ + +//fprintf(stdout, "Key NS=%d MB=%d S=%d\n", TCL_CONVERT_NOSPACE, +//TCL_CONVERT_MULTIBYTE, TCL_CONVERT_SYNTAX); fflush(stdout); +//fprintf(stdout, "Decoded %d %d\n", dstDecoded,code); fflush(stdout); + dstWrote = dstRead = dstDecoded; + TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); + + if (dstRead < dstDecoded) { + + /* + * The encoding transformation produced bytes that the + * translation transformation did not consume. Start over + * and impose new limits so that doesn't happen again. + */ +//fprintf(stdout, "X! %d %d\n", dstRead, dstDecoded); fflush(stdout); + + dstLimit = dstRead + TCL_UTF_MAX; + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + continue; + } + +//fprintf(stdout, "check %d %d %d\n", dstWrote, dstRead, dstDecoded); +//fflush(stdout); + + /* + * The translation transformation can only reduce the number + * of chars when it converts \r\n into \n. The reduction in + * the number of chars is the difference in bytes read and written. + */ + + numChars -= (dstRead - dstWrote); + + if (charsToRead > 0 && numChars > charsToRead) { + + /* + * We read more chars than allowed. Reset limits to + * prevent that and try again. + */ +//fprintf(stdout, "Y!\n"); fflush(stdout); + + dstLimit = Tcl_UtfAtIndex(dst, charsToRead + 1) - dst; + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + continue; + } + + if (dstWrote == 0) { + +//fprintf(stdout, "Z!\n"); fflush(stdout); + /* + * Could not read anything. Ask caller to get more data. + */ + + return -1; + } + + statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; + + bufPtr->nextRemoved += srcRead; + if (dstWrote > srcRead + 1) { + *factorPtr = dstWrote * UTF_EXPANSION_FACTOR / srcRead; + } + *offsetPtr += dstWrote; +//fprintf(stdout, "OK: %d\n", numChars); fflush(stdout); + return numChars; + } + +#else /* * [Bug 1462248]: The cause of the crash reported in this bug is this: * @@ -5560,6 +5674,7 @@ ReadChars( } *offsetPtr += dstWrote; return numChars; +#endif } /* @@ -5661,7 +5776,9 @@ TranslateInputEOL( if (*src == '\r') { src++; if (src >= srcMax) { - SetFlag(statePtr, INPUT_NEED_NL); +// SetFlag(statePtr, INPUT_NEED_NL); +//fprintf(stdout, "BREAK!\n"); fflush(stdout); +src--; break; } else if (*src == '\n') { *dst++ = *src++; } else { @@ -5673,6 +5790,7 @@ TranslateInputEOL( } srcLen = src - srcStart; dstLen = dst - dstStart; +//fprintf(stdout, "eh? %d %d\n", srcLen, dstLen); fflush(stdout); break; } case TCL_TRANSLATE_AUTO: { -- cgit v0.12 From 607601abc11ec2e965fedc5d3cb1e6d83c3a4a10 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 28 Feb 2014 18:25:20 +0000 Subject: More ReadChars rewriting. Test suite now passes. Note that this reform simplifies ReadChars a fair bit (at least in my eyes). Also it does away with the use of an INPUT_NEED_NL flag, using the same strategy for partial \r\n sequences as is used for incomplete multibyte chars. --- generic/tclIO.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 173 insertions(+), 21 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 20428b5..ec71991 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5403,7 +5403,6 @@ ReadChars( * srcLen bytes, write no more than dstLimit bytes. */ -//fprintf(stdout, "Start %d %d\n", dstLimit, srcLen); fflush(stdout); int code = Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, statePtr->inputEncodingFlags & (bufPtr->nextPtr ? ~0 : ~TCL_ENCODING_END), &statePtr->inputEncodingState, @@ -5416,9 +5415,6 @@ ReadChars( * Capture the number of bytes actually consumed in dstRead. */ -//fprintf(stdout, "Key NS=%d MB=%d S=%d\n", TCL_CONVERT_NOSPACE, -//TCL_CONVERT_MULTIBYTE, TCL_CONVERT_SYNTAX); fflush(stdout); -//fprintf(stdout, "Decoded %d %d\n", dstDecoded,code); fflush(stdout); dstWrote = dstRead = dstDecoded; TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); @@ -5426,20 +5422,144 @@ ReadChars( /* * The encoding transformation produced bytes that the - * translation transformation did not consume. Start over - * and impose new limits so that doesn't happen again. + * translation transformation did not consume. Why did + * this happen? */ -//fprintf(stdout, "X! %d %d\n", dstRead, dstDecoded); fflush(stdout); - dstLimit = dstRead + TCL_UTF_MAX; - statePtr->flags = savedFlags; - statePtr->inputEncodingFlags = savedIEFlags; - statePtr->inputEncodingState = savedState; - continue; - } + if (statePtr->inEofChar && dst[dstRead] == statePtr->inEofChar) { + /* + * 1) There's an eof char set on the channel, and + * we saw it and stopped translating at that point. + * + * NOTE the bizarre spec of TranslateInputEOL in this case. + * Clearly the eof char had to be read in order to account + * for the stopping, but the value of dstRead does not + * include it. + * + * Also rather bizarre, our caller can only notice an + * EOF condition if we return the value -1 as the number + * of chars read. This forces us to perform a 2-call + * dance where the first call can read all the chars + * up to the eof char, and the second call is solely + * for consuming the encoded eof char then pointed at + * by src so that we can return that magic -1 value. + * This seems really wasteful, especially since + * the first decoding pass of each call is likely to + * decode many bytes beyond that eof char that's all we + * care about. + */ + + if (dstRead == 0) { + /* + * Curious choice in the eof char handling. We leave + * the eof char in the buffer. So, no need to compute + * a proper srcRead value. At this point, there + * are no chars before the eof char in the buffer. + */ + return -1; + } + + { + /* + * There are chars leading the buffer before the eof + * char. Adjust the dstLimit so we go back and read + * only those and do not encounter the eof char this + * time. + */ + + dstLimit = dstRead + TCL_UTF_MAX; + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + continue; + } + } + + /* + * 2) The other way to read fewer bytes than are decoded + * is when the final byte is \r and we're in a CRLF + * translation mode so we cannot decide whether to + * record \r or \n yet. + */ + + assert(dstRead + 1 == dstDecoded); + assert(dst[dstRead] == '\r'); + assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); + + if (dstWrote > 0) { + /* + * There are chars we can read before we hit the bare cr. + * Go back with a smaller dstLimit so we get them in the + * next pass, compute a matching srcRead, and don't end + * up back here in this call. + */ + + dstLimit = dstRead + TCL_UTF_MAX; + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + continue; + } + + assert(dstWrote == 0); + assert(dstRead == 0); + assert(dstDecoded == 1); + + /* + * We decoded only the bare cr, and we cannot read a + * translated char from that alone. We have to know what's + * next. So why do we only have the one decoded char? + */ + + if (code != TCL_OK) { + char buffer[TCL_UTF_MAX + 2]; + int read, decoded, count; + + /* + * Didn't get everything the buffer could offer + */ + + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + + Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, + statePtr->inputEncodingFlags & (bufPtr->nextPtr + ? ~0 : ~TCL_ENCODING_END), &statePtr->inputEncodingState, + buffer, TCL_UTF_MAX + 2, &read, &decoded, &count); + + if (count == 2) { + if (buffer[1] == '\n') { + /* \r\n translate to \n */ + dst[0] = '\n'; + bufPtr->nextRemoved += read; + } else { + dst[0] = '\r'; + bufPtr->nextRemoved += srcRead; + } + + dst[1] = '\0'; + statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; + + *offsetPtr += 1; + return 1; + } -//fprintf(stdout, "check %d %d %d\n", dstWrote, dstRead, dstDecoded); -//fflush(stdout); + } else if (statePtr->flags & CHANNEL_EOF) { + + /* + * The bare \r is the only char and we will never read + * a subsequent char to make the determination. + */ + + dst[0] = '\r'; + bufPtr->nextRemoved = bufPtr->nextAdded; + *offsetPtr += 1; + return 1; + } + + /* FALL THROUGH - get more data (dstWrote == 0) */ + } /* * The translation transformation can only reduce the number @@ -5455,7 +5575,6 @@ ReadChars( * We read more chars than allowed. Reset limits to * prevent that and try again. */ -//fprintf(stdout, "Y!\n"); fflush(stdout); dstLimit = Tcl_UtfAtIndex(dst, charsToRead + 1) - dst; statePtr->flags = savedFlags; @@ -5466,12 +5585,46 @@ ReadChars( if (dstWrote == 0) { -//fprintf(stdout, "Z!\n"); fflush(stdout); - /* - * Could not read anything. Ask caller to get more data. + /* + * We were not able to read any chars. Maybe there were + * not enough src bytes to decode into a char. Maybe + * a lone \r could not be translated (crlf mode). Need + * to combine any unused src bytes we have in the first + * buffer with subsequent bytes to try again. */ - return -1; + ChannelBuffer *nextPtr = bufPtr->nextPtr; + + if (nextPtr == NULL) { + if (srcLen > 0) { + SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); + } + return -1; + } + + /* + * Space is made at the beginning of the buffer to copy the + * previous unused bytes there. Check first if the buffer we + * are using actually has enough space at its beginning for + * the data we are copying. Because if not we will write over + * the buffer management information, especially the 'nextPtr'. + * + * Note that the BUFFER_PADDING (See AllocChannelBuffer) is + * used to prevent exactly this situation. I.e. it should never + * happen. Therefore it is ok to panic should it happen despite + * the precautions. + */ + + if (nextPtr->nextRemoved - srcLen < 0) { + Tcl_Panic("Buffer Underflow, BUFFER_PADDING not enough"); + } + + nextPtr->nextRemoved -= srcLen; + memcpy(RemovePoint(nextPtr), src, (size_t) srcLen); + RecycleBuffer(statePtr, bufPtr, 0); + statePtr->inQueueHead = nextPtr; + return ReadChars(statePtr, objPtr, charsToRead, + offsetPtr, factorPtr); } statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; @@ -5481,7 +5634,6 @@ ReadChars( *factorPtr = dstWrote * UTF_EXPANSION_FACTOR / srcRead; } *offsetPtr += dstWrote; -//fprintf(stdout, "OK: %d\n", numChars); fflush(stdout); return numChars; } -- cgit v0.12 From 7b66d219bab6b6710a22b4b18ca563239ffdc050 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 28 Feb 2014 18:28:16 +0000 Subject: tidy up. --- generic/tclIO.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index ec71991..a0a349f 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5928,9 +5928,8 @@ TranslateInputEOL( if (*src == '\r') { src++; if (src >= srcMax) { -// SetFlag(statePtr, INPUT_NEED_NL); -//fprintf(stdout, "BREAK!\n"); fflush(stdout); -src--; break; + src--; + break; } else if (*src == '\n') { *dst++ = *src++; } else { @@ -5942,7 +5941,6 @@ src--; break; } srcLen = src - srcStart; dstLen = dst - dstStart; -//fprintf(stdout, "eh? %d %d\n", srcLen, dstLen); fflush(stdout); break; } case TCL_TRANSLATE_AUTO: { -- cgit v0.12 From bb1b4fcb06f80fddfd136a9bd14bf64808f45971 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 28 Feb 2014 18:36:00 +0000 Subject: another coverage test. --- tests/io.test | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/io.test b/tests/io.test index 0941e02..c325809 100644 --- a/tests/io.test +++ b/tests/io.test @@ -4772,6 +4772,21 @@ test io-35.19 {Tcl_Eof, eof char in middle, cr write, crlf read} { close $f list $c $l $e [scan [string index $in end] %c] } {17 8 1 13} +test io-35.20 {Tcl_Eof, eof char in middle, cr write, crlf read} { + file delete $path(test1) + set f [open $path(test1) w] + fconfigure $f -translation cr -eofchar {} + set i [format \n%cqrsuvw 26] + puts $f $i + close $f + set c [file size $path(test1)] + set f [open $path(test1) r] + fconfigure $f -translation crlf -eofchar \x1a + set l [string length [set in [read $f]]] + set e [eof $f] + close $f + list $c $l $e [scan [string index $in end] %c] +} {9 1 1 13} # Test Tcl_InputBlocked -- cgit v0.12 From 6ac36ed52bd548be97ae7baa3022e822f6a1bdce Mon Sep 17 00:00:00 2001 From: dgp Date: Sat, 1 Mar 2014 03:01:41 +0000 Subject: Fixups make the test suite almost pass (except *io-39.17) --- generic/tclIO.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 7b798af..139a05e 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5289,7 +5289,7 @@ ReadChars( dst = TclGetString(objPtr) + numBytes; } -#if 0 +#if 1 /* * This routine is burdened with satisfying several constraints. @@ -5373,6 +5373,7 @@ ReadChars( * a proper srcRead value. At this point, there * are no chars before the eof char in the buffer. */ + Tcl_SetObjLength(objPtr, numBytes); return -1; } @@ -5459,7 +5460,6 @@ ReadChars( statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; Tcl_SetObjLength(objPtr, numBytes + 1); - // *offsetPtr += 1; return 1; } @@ -5473,7 +5473,6 @@ ReadChars( dst[0] = '\r'; bufPtr->nextRemoved = bufPtr->nextAdded; Tcl_SetObjLength(objPtr, numBytes + 1); - //*offsetPtr += 1; return 1; } @@ -5518,6 +5517,7 @@ ReadChars( if (srcLen > 0) { SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); } + Tcl_SetObjLength(objPtr, numBytes); return -1; } @@ -5542,6 +5542,7 @@ ReadChars( memcpy(RemovePoint(nextPtr), src, (size_t) srcLen); RecycleBuffer(statePtr, bufPtr, 0); statePtr->inQueueHead = nextPtr; + Tcl_SetObjLength(objPtr, numBytes); return ReadChars(statePtr, objPtr, charsToRead, factorPtr); } @@ -5552,7 +5553,6 @@ ReadChars( *factorPtr = dstWrote * UTF_EXPANSION_FACTOR / srcRead; } Tcl_SetObjLength(objPtr, numBytes + dstWrote); - //*offsetPtr += dstWrote; return numChars; } @@ -5850,9 +5850,8 @@ TranslateInputEOL( if (*src == '\r') { src++; if (src >= srcMax) { -SetFlag(statePtr, INPUT_NEED_NL); -// src--; -// break; + src--; + break; } else if (*src == '\n') { *dst++ = *src++; } else { -- cgit v0.12 From 23801213cacd306b0bfddbdb51efcd15c88ed0f9 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 5 Mar 2014 14:23:38 +0000 Subject: Merge repair to correct failing tests. --- generic/tclIO.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 139a05e..6e3f0cf 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5320,7 +5320,7 @@ ReadChars( * srcLen bytes, write no more than dstLimit bytes. */ - int code = Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, + int code = Tcl_ExternalToUtf(NULL, encoding, src, srcLen, statePtr->inputEncodingFlags & (bufPtr->nextPtr ? ~0 : ~TCL_ENCODING_END), &statePtr->inputEncodingState, dst, dstLimit, &srcRead, &dstDecoded, &numChars); @@ -5441,7 +5441,7 @@ ReadChars( statePtr->inputEncodingFlags = savedIEFlags; statePtr->inputEncodingState = savedState; - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, + Tcl_ExternalToUtf(NULL, encoding, src, srcLen, statePtr->inputEncodingFlags & (bufPtr->nextPtr ? ~0 : ~TCL_ENCODING_END), &statePtr->inputEncodingState, buffer, TCL_UTF_MAX + 2, &read, &decoded, &count); -- cgit v0.12 From 9d31d410437d7e7fad1201c869e0a7c479daf693 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 5 Mar 2014 17:16:35 +0000 Subject: Adapt CopyAndTranslateBuffer() to changes in TranslateInputEOL(). Notably no longer using the INPUT_NEED_NL flag. --- generic/tclIO.c | 117 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 6e3f0cf..013f8dd 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5914,7 +5914,7 @@ TranslateInputEOL( SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; - ResetFlag(statePtr, INPUT_SAW_CR | INPUT_NEED_NL); + ResetFlag(statePtr, INPUT_SAW_CR); return 1; } @@ -9046,7 +9046,6 @@ CopyAndTranslateBuffer( * in the current input buffer? */ int copied; /* How many characters were already copied * into the destination space? */ - int toCopy; /* * If there is no input at all, return zero. The invariant is that either @@ -9061,51 +9060,90 @@ CopyAndTranslateBuffer( bufPtr = statePtr->inQueueHead; bytesInBuffer = BytesLeft(bufPtr); - copied = 0; -#if 0 - if (statePtr->flags & INPUT_NEED_NL) { +#if 1 + copied = space; + if (bytesInBuffer <= copied) { + copied = bytesInBuffer; + } + if (copied == 0) { + return copied; + } + TranslateInputEOL(statePtr, result, RemovePoint(bufPtr), + &copied, &bytesInBuffer); + bufPtr->nextRemoved += bytesInBuffer; - /* - * An earlier call to TranslateInputEOL ended in the read of a \r . - * Only the next read from the same channel can complete the - * translation sequence to tell us what character we should read. - */ + /* + * If the current buffer is empty recycle it. + */ + + if (IsBufferEmpty(bufPtr)) { + statePtr->inQueueHead = bufPtr->nextPtr; + if (statePtr->inQueueHead == NULL) { + statePtr->inQueueTail = NULL; + } + RecycleBuffer(statePtr, bufPtr, 0); + } else { + + if (copied > 0) { + return copied; + } - if (bytesInBuffer) { - /* There's a next byte. It will settle things. */ - ResetFlag(statePtr, INPUT_NEED_NL); + if (statePtr->inEofChar + && RemovePoint(bufPtr)[0] == statePtr->inEofChar) { + return 0; + } - if (RemovePoint(bufPtr)[0] == '\n') { - bufPtr->nextRemoved++; - bytesInBuffer--; - *result++ = '\n'; - } else { - *result++ = '\r'; + if (BytesLeft(bufPtr) == 1) { + + ChannelBuffer *nextPtr = bufPtr->nextPtr; + + if (nextPtr == NULL) { + + if (statePtr->flags & CHANNEL_EOF) { + *result = '\r'; + bufPtr->nextRemoved += 1; + return 1; + } + + SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); + return 0; } - copied++; - space--; - } else if (statePtr->flags & CHANNEL_EOF) { - /* There is no next byte, and there never will be (EOF). */ - ResetFlag(statePtr, INPUT_NEED_NL); + + nextPtr->nextRemoved -= 1; + memcpy(RemovePoint(nextPtr), RemovePoint(bufPtr), 1); + RecycleBuffer(statePtr, bufPtr, 0); + statePtr->inQueueHead = nextPtr; + return 0; + } + + if (statePtr->inEofChar + && RemovePoint(bufPtr)[1] == statePtr->inEofChar) { *result = '\r'; + bufPtr->nextRemoved += 1; return 1; - } else { - /* There is no next byte. Ask the caller to read more. */ - return 0; } + /* + * Buffer is not empty. How can that be? + * 0) We stopped early due to the value of "space". + * => copied > 0 and all is fine. + * 1) We saw eof char and stopped the translation copy. + * => if (copied > 0) or ((copied == 0) and @ eof char), + * return is fine. + * 2) The buffer holds a \r while in CRLF translation, followed + * by either the end of the buffer, or the eof char. + */ + } - toCopy = space; - if (bytesInBuffer <= toCopy) { - toCopy = bytesInBuffer; - } - if (toCopy == 0) { - return copied; - } - TranslateInputEOL(statePtr, result, RemovePoint(bufPtr), - &toCopy, &bytesInBuffer); - bufPtr->nextRemoved += bytesInBuffer; - copied += toCopy; + + /* + * Return the number of characters copied into the result buffer. This may + * be different from the number of bytes consumed, because of EOL + * translations. + */ + + return copied; #else + copied = 0; switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: if (bytesInBuffer == 0) { @@ -9265,7 +9303,6 @@ CopyAndTranslateBuffer( } } } -#endif /* * If the current buffer is empty recycle it. @@ -9286,6 +9323,7 @@ CopyAndTranslateBuffer( */ return copied; +#endif } /* @@ -10726,7 +10764,6 @@ DumpFlags( ChanFlag('S', CHANNEL_STICKY_EOF); ChanFlag('B', CHANNEL_BLOCKED); ChanFlag('/', INPUT_SAW_CR); - ChanFlag('*', INPUT_NEED_NL); ChanFlag('D', CHANNEL_DEAD); ChanFlag('R', CHANNEL_RAW_MODE); #ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING -- cgit v0.12 From 94a4d6ac65eed79a3fe89a71d1c2a429793300bc Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 5 Mar 2014 19:41:51 +0000 Subject: Remove old dead code; silence compiler warnings; tidy up. --- generic/tclIO.c | 415 ++------------------------------------------------------ generic/tclIO.h | 3 - 2 files changed, 12 insertions(+), 406 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 013f8dd..821d111 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5252,33 +5252,25 @@ ReadChars( * UTF-8. On output, contains another guess * based on the data seen so far. */ { - int toRead, factor, srcLen, dstNeeded, numBytes; - int srcRead, dstWrote, numChars, dstRead; - ChannelBuffer *bufPtr; - char *src, *dst; - Tcl_EncodingState oldState; - int encEndFlagSuppressed = 0; Tcl_Encoding encoding = statePtr->encoding? statePtr->encoding : GetBinaryEncoding(); - - factor = *factorPtr; - - bufPtr = statePtr->inQueueHead; - src = RemovePoint(bufPtr); - srcLen = BytesLeft(bufPtr); - - toRead = charsToRead; - if ((unsigned)toRead > (unsigned)srcLen) { - toRead = srcLen; - } + Tcl_EncodingState savedState = statePtr->inputEncodingState; + ChannelBuffer *bufPtr = statePtr->inQueueHead; + int savedIEFlags = statePtr->inputEncodingFlags; + int savedFlags = statePtr->flags; + char *dst, *src = RemovePoint(bufPtr); + int dstLimit, numBytes, srcLen = BytesLeft(bufPtr); + int toRead = ((unsigned) charsToRead > srcLen) ? srcLen : charsToRead; /* * 'factor' is how much we guess that the bytes in the source buffer will * expand when converted to UTF-8 chars. This guess comes from analyzing * how many characters were produced by the previous pass. */ + + int factor = *factorPtr; + int dstNeeded = TCL_UTF_MAX - 1 + toRead * factor / UTF_EXPANSION_FACTOR; - dstNeeded = TCL_UTF_MAX - 1 + toRead * factor / UTF_EXPANSION_FACTOR; (void) TclGetStringFromObj(objPtr, &numBytes); Tcl_AppendToObj(objPtr, NULL, dstNeeded); if (toRead == srcLen) { @@ -5289,8 +5281,6 @@ ReadChars( dst = TclGetString(objPtr) + numBytes; } -#if 1 - /* * This routine is burdened with satisfying several constraints. * It cannot append more than 'charsToRead` chars onto objPtr. @@ -5307,13 +5297,9 @@ ReadChars( * a consistent set of results. This takes the shape of a loop. */ - int dstLimit = dstNeeded + 1; - int savedFlags = statePtr->flags; - int savedIEFlags = statePtr->inputEncodingFlags; - Tcl_EncodingState savedState = statePtr->inputEncodingState; - + dstLimit = dstNeeded + 1; while (1) { - int dstDecoded; + int dstDecoded, dstRead, dstWrote, srcRead, numChars; /* * Perform the encoding transformation. Read no more than @@ -5555,200 +5541,6 @@ ReadChars( Tcl_SetObjLength(objPtr, numBytes + dstWrote); return numChars; } - -#else - /* - * [Bug 1462248]: The cause of the crash reported in this bug is this: - * - * - ReadChars, called with a single buffer, with a incomplete - * multi-byte character at the end (only the first byte of it). - * - Encoding translation fails, asks for more data - * - Data is read, and eof is reached, TCL_ENCODING_END (TEE) is set. - * - ReadChar is called again, converts the first buffer, but due to TEE - * it does not check for incomplete multi-byte data, and the character - * just after the end of the first buffer is a valid completion of the - * multi-byte header in the actual buffer. The conversion reads more - * characters from the buffer then present. This causes nextRemoved to - * overshoot nextAdded and the next reads compute a negative srcLen, - * cause further translations to fail, causing copying of data into the - * next buffer using bad arguments, causing the mecpy for to eventually - * fail. - * - * In the end it is a memory access bug spiraling out of control if the - * conditions are _just so_. And ultimate cause is that TEE is given to a - * conversion where it should not. TEE signals that this is the last - * buffer. Except in our case it is not. - * - * My solution is to suppress TEE if the first buffer is not the last. We - * will eventually need it given that EOF has been reached, but not right - * now. This is what the new flag "endEncSuppressFlag" is for. - * - * The bug in 'Tcl_Utf2UtfProc' where it read from memory behind the - * actual buffer has been fixed as well, and fixes the problem with the - * crash too, but this would still allow the generic layer to - * accidentially break a multi-byte sequence if the conditions are just - * right, because again the ExternalToUtf would be successful where it - * should not. - */ - - if ((statePtr->inputEncodingFlags & TCL_ENCODING_END) && - (bufPtr->nextPtr != NULL)) { - /* - * TEE is set for a buffer which is not the last. Squash it for now, - * and restore it later, before yielding control to our caller. - */ - - statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; - encEndFlagSuppressed = 1; - } - - oldState = statePtr->inputEncodingState; - if (statePtr->flags & INPUT_NEED_NL) { - /* - * We want a '\n' because the last character we saw was '\r'. - */ - - ResetFlag(statePtr, INPUT_NEED_NL); - Tcl_ExternalToUtf(NULL, encoding, src, srcLen, - statePtr->inputEncodingFlags, &statePtr->inputEncodingState, - dst, TCL_UTF_MAX + 1, &srcRead, &dstWrote, &numChars); - if ((dstWrote > 0) && (*dst == '\n')) { - /* - * The next char was a '\n'. Consume it and produce a '\n'. - */ - - bufPtr->nextRemoved += srcRead; - } else { - /* - * The next char was not a '\n'. Produce a '\r'. - */ - - *dst = '\r'; - } - statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; - Tcl_SetObjLength(objPtr, numBytes + 1); - - if (encEndFlagSuppressed) { - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - } - return 1; - } - - Tcl_ExternalToUtf(NULL, encoding, src, srcLen, - statePtr->inputEncodingFlags, &statePtr->inputEncodingState, dst, - dstNeeded + 1, &srcRead, &dstWrote, &numChars); - - if (encEndFlagSuppressed) { - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - } - - if (srcRead == 0) { - /* - * Not enough bytes in src buffer to make a complete char. Copy the - * bytes to the next buffer to make a new contiguous string, then tell - * the caller to fill the buffer with more bytes. - */ - - ChannelBuffer *nextPtr; - - nextPtr = bufPtr->nextPtr; - if (nextPtr == NULL) { - if (srcLen > 0) { - /* - * There isn't enough data in the buffers to complete the next - * character, so we need to wait for more data before the next - * file event can be delivered. [Bug 478856] - * - * The exception to this is if the input buffer was completely - * empty before we tried to convert its contents. Nothing in, - * nothing out, and no incomplete character data. The - * conversion before the current one was complete. - */ - - SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); - } - Tcl_SetObjLength(objPtr, numBytes); - return -1; - } - - /* - * Space is made at the beginning of the buffer to copy the previous - * unused bytes there. Check first if the buffer we are using actually - * has enough space at its beginning for the data we are copying. - * Because if not we will write over the buffer management - * information, especially the 'nextPtr'. - * - * Note that the BUFFER_PADDING (See AllocChannelBuffer) is used to - * prevent exactly this situation. I.e. it should never happen. - * Therefore it is ok to panic should it happen despite the - * precautions. - */ - - if (nextPtr->nextRemoved - srcLen < 0) { - Tcl_Panic("Buffer Underflow, BUFFER_PADDING not enough"); - } - - nextPtr->nextRemoved -= srcLen; - memcpy(RemovePoint(nextPtr), src, (size_t) srcLen); - RecycleBuffer(statePtr, bufPtr, 0); - statePtr->inQueueHead = nextPtr; - Tcl_SetObjLength(objPtr, numBytes); - return ReadChars(statePtr, objPtr, charsToRead, factorPtr); - } - - dstRead = dstWrote; - if (TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead) != 0) { - /* - * Hit EOF char. How many bytes of src correspond to where the EOF was - * located in dst? Run the conversion again with an output buffer just - * big enough to hold the data so we can get the correct value for - * srcRead. - */ - - if (dstWrote == 0) { - Tcl_SetObjLength(objPtr, numBytes); - return -1; - } - statePtr->inputEncodingState = oldState; - Tcl_ExternalToUtf(NULL, encoding, src, srcLen, - statePtr->inputEncodingFlags, &statePtr->inputEncodingState, - dst, dstRead + TCL_UTF_MAX, &srcRead, &dstWrote, &numChars); - TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); - } - - /* - * The number of characters that we got may be less than the number that - * we started with because "\r\n" sequences may have been turned into just - * '\n' in dst. - */ - - numChars -= (dstRead - dstWrote); - - if ((unsigned) numChars > (unsigned) toRead) { - /* - * Got too many chars. - */ - - const char *eof; - - eof = Tcl_UtfAtIndex(dst, toRead); - statePtr->inputEncodingState = oldState; - Tcl_ExternalToUtf(NULL, encoding, src, srcLen, - statePtr->inputEncodingFlags, &statePtr->inputEncodingState, - dst, eof - dst + TCL_UTF_MAX, &srcRead, &dstWrote, &numChars); - dstRead = dstWrote; - TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); - numChars -= (dstRead - dstWrote); - } - statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; - - bufPtr->nextRemoved += srcRead; - if (dstWrote > srcRead + 1) { - *factorPtr = dstWrote * UTF_EXPANSION_FACTOR / srcRead; - } - Tcl_SetObjLength(objPtr, numBytes + dstWrote); - return numChars; -#endif } /* @@ -9060,7 +8852,6 @@ CopyAndTranslateBuffer( bufPtr = statePtr->inQueueHead; bytesInBuffer = BytesLeft(bufPtr); -#if 1 copied = space; if (bytesInBuffer <= copied) { copied = bytesInBuffer; @@ -9142,188 +8933,6 @@ CopyAndTranslateBuffer( */ return copied; -#else - copied = 0; - switch (statePtr->inputTranslation) { - case TCL_TRANSLATE_LF: - if (bytesInBuffer == 0) { - return 0; - } - - /* - * Copy the current chunk into the result buffer. - */ - - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - memcpy(result, RemovePoint(bufPtr), (size_t) space); - bufPtr->nextRemoved += space; - copied = space; - break; - case TCL_TRANSLATE_CR: { - char *end; - - if (bytesInBuffer == 0) { - return 0; - } - - /* - * Copy the current chunk into the result buffer, then replace all \r - * with \n. - */ - - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - memcpy(result, RemovePoint(bufPtr), (size_t) space); - bufPtr->nextRemoved += space; - copied = space; - - for (end = result + copied; result < end; result++) { - if (*result == '\r') { - *result = '\n'; - } - } - break; - } - case TCL_TRANSLATE_CRLF: { - char *src, *end, *dst; - int curByte; - - /* - * If there is a held-back "\r" at EOF, produce it now. - */ - - if (bytesInBuffer == 0) { - if ((statePtr->flags & (INPUT_SAW_CR | CHANNEL_EOF)) == - (INPUT_SAW_CR | CHANNEL_EOF)) { - result[0] = '\r'; - ResetFlag(statePtr, INPUT_SAW_CR); - return 1; - } - return 0; - } - - /* - * Copy the current chunk and replace "\r\n" with "\n" (but not - * standalone "\r"!). - */ - - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - memcpy(result, RemovePoint(bufPtr), (size_t) space); - bufPtr->nextRemoved += space; - copied = space; - - end = result + copied; - dst = result; - for (src = result; src < end; src++) { - curByte = *src; - if (curByte == '\n') { - ResetFlag(statePtr, INPUT_SAW_CR); - } else if (statePtr->flags & INPUT_SAW_CR) { - ResetFlag(statePtr, INPUT_SAW_CR); - *dst = '\r'; - dst++; - } - if (curByte == '\r') { - SetFlag(statePtr, INPUT_SAW_CR); - } else { - *dst = (char) curByte; - dst++; - } - } - copied = dst - result; - break; - } - case TCL_TRANSLATE_AUTO: { - char *src, *end, *dst; - int curByte; - - if (bytesInBuffer == 0) { - return 0; - } - - /* - * Loop over the current buffer, converting "\r" and "\r\n" to "\n". - */ - - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - memcpy(result, RemovePoint(bufPtr), (size_t) space); - bufPtr->nextRemoved += space; - copied = space; - - end = result + copied; - dst = result; - for (src = result; src < end; src++) { - curByte = *src; - if (curByte == '\r') { - SetFlag(statePtr, INPUT_SAW_CR); - *dst = '\n'; - dst++; - } else { - if ((curByte != '\n') || !(statePtr->flags & INPUT_SAW_CR)) { - *dst = (char) curByte; - dst++; - } - ResetFlag(statePtr, INPUT_SAW_CR); - } - } - copied = dst - result; - break; - } - default: - Tcl_Panic("unknown eol translation mode"); - } - - /* - * If an in-stream EOF character is set for this channel, check that the - * input we copied so far does not contain the EOF char. If it does, copy - * only up to and excluding that character. - */ - - if (statePtr->inEofChar != 0) { - int i; - - for (i = 0; i < copied; i++) { - if (result[i] == (char) statePtr->inEofChar) { - /* - * Set sticky EOF so that no further input is presented to the - * caller. - */ - - SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - copied = i; - break; - } - } - } - - /* - * If the current buffer is empty recycle it. - */ - - if (IsBufferEmpty(bufPtr)) { - statePtr->inQueueHead = bufPtr->nextPtr; - if (statePtr->inQueueHead == NULL) { - statePtr->inQueueTail = NULL; - } - RecycleBuffer(statePtr, bufPtr, 0); - } - - /* - * Return the number of characters copied into the result buffer. This may - * be different from the number of bytes consumed, because of EOL - * translations. - */ - - return copied; -#endif } /* diff --git a/generic/tclIO.h b/generic/tclIO.h index ebf2ef7..a57d4c5 100644 --- a/generic/tclIO.h +++ b/generic/tclIO.h @@ -252,9 +252,6 @@ typedef struct ChannelState { #define INPUT_SAW_CR (1<<12) /* Channel is in CRLF eol input * translation mode and the last byte * seen was a "\r". */ -#define INPUT_NEED_NL (1<<15) /* Saw a '\r' at end of last buffer, - * and there should be a '\n' at - * beginning of next buffer. */ #define CHANNEL_DEAD (1<<13) /* The channel has been closed by the * exit handler (on exit) but not * deallocated. When any IO operation -- cgit v0.12 From a59f5c70234be1134e3752519daa601c3c850365 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 6 Mar 2014 15:51:06 +0000 Subject: Variable "rawStart" serves no purpose. --- generic/tclIO.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 821d111..b0d0e32 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4464,7 +4464,7 @@ FilterInputBytes( ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; - char *raw, *rawStart, *dst; + char *raw, *dst; int offset, toRead, dstNeeded, spaceLeft, result, rawLen; Tcl_Obj *objPtr; #define ENCODING_LINESIZE 20 /* Lower bound on how many bytes to convert at @@ -4521,8 +4521,7 @@ FilterInputBytes( * string rep if we need more space. */ - rawStart = RemovePoint(bufPtr); - raw = rawStart; + raw = RemovePoint(bufPtr); rawLen = BytesLeft(bufPtr); dst = *gsPtr->dstPtr; -- cgit v0.12 From a895183137cb5e741f92353116465a9e27c432e4 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 7 Mar 2014 19:43:16 +0000 Subject: Simplify the input eof char scan. Update some comments. --- generic/tclIO.c | 64 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index b0d0e32..03aac32 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5244,7 +5244,11 @@ ReadChars( * is larger than the number of characters * available in the first buffer, only the * characters from the first buffer are - * returned. */ + * returned. The execption is when there is + * not any complete character in the first + * buffer. In that case, a recursive call + * effectively obtains chars from the + * second buffer. */ int *factorPtr) /* On input, contains a guess of how many * bytes need to be allocated to hold the * result of converting N source bytes to @@ -5259,6 +5263,15 @@ ReadChars( int savedFlags = statePtr->flags; char *dst, *src = RemovePoint(bufPtr); int dstLimit, numBytes, srcLen = BytesLeft(bufPtr); + + /* + * One src byte can yield at most one character. So when the + * number of src bytes we plan to read is less than the limit on + * character count to be read, clearly we will remain within that + * limit, and we can use the value of "srcLen" as a tighter limit + * for sizing receiving buffers. + */ + int toRead = ((unsigned) charsToRead > srcLen) ? srcLen : charsToRead; /* @@ -5569,43 +5582,32 @@ TranslateInputEOL( * characters. */ const char *srcStart, /* Source characters. */ int *dstLenPtr, /* On entry, the maximum length of output - * buffer in bytes; must be <= *srcLenPtr. On - * exit, the number of bytes actually used in - * output buffer. */ + * buffer in bytes. On exit, the number of + * bytes actually used in output buffer. */ int *srcLenPtr) /* On entry, the length of source buffer. On * exit, the number of bytes read from the * source buffer. */ { - int dstLen, srcLen, inEofChar; - const char *eof; + const char *eof = NULL; + int dstLen = *dstLenPtr; + int srcLen = *srcLenPtr; + int inEofChar = statePtr->inEofChar; - dstLen = *dstLenPtr; - - eof = NULL; - inEofChar = statePtr->inEofChar; if (inEofChar != '\0') { /* - * Find EOF in translated buffer then compress out the EOL. The source - * buffer may be much longer than the destination buffer - we only - * want to return EOF if the EOF has been copied to the destination - * buffer. + * Make sure we do not read past any logical end of channel input + * created by the presence of the input eof char. */ - const char *src, *srcMax; - - srcMax = srcStart + *srcLenPtr; - for (src = srcStart; src < srcMax; src++) { - if (*src == inEofChar) { - eof = src; - srcLen = src - srcStart; - if (srcLen < dstLen) { - dstLen = srcLen; - } - *srcLenPtr = srcLen; - break; - } + if ((eof = memchr(srcStart, inEofChar, srcLen))) { + srcLen = eof - srcStart; } } + + if (dstLen > srcLen) { + dstLen = srcLen; + } + switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: if (dstStart != srcStart) { @@ -5635,7 +5637,7 @@ TranslateInputEOL( dst = dstStart; src = srcStart; srcEnd = srcStart + dstLen; - srcMax = srcStart + *srcLenPtr; + srcMax = srcStart + srcLen; for ( ; src < srcEnd; ) { if (*src == '\r') { @@ -5663,7 +5665,7 @@ TranslateInputEOL( dst = dstStart; src = srcStart; srcEnd = srcStart + dstLen; - srcMax = srcStart + *srcLenPtr; + srcMax = srcStart + srcLen; if ((statePtr->flags & INPUT_SAW_CR) && (src < srcMax)) { if (*src == '\n') { @@ -5692,9 +5694,10 @@ TranslateInputEOL( break; } default: - return 0; + Tcl_Panic("unknown input translation %d", statePtr->inputTranslation); } *dstLenPtr = dstLen; + *srcLenPtr = srcLen; if ((eof != NULL) && (srcStart + srcLen >= eof)) { /* @@ -5709,7 +5712,6 @@ TranslateInputEOL( return 1; } - *srcLenPtr = srcLen; return 0; } -- cgit v0.12 From 83f5493faa96da87b5327be1f49e432f5a870879 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 7 Mar 2014 20:15:12 +0000 Subject: TranslateInputEOL() callers no longer need assert dstLen <= srcLen. --- generic/tclIO.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 03aac32..d23ca03 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5330,7 +5330,8 @@ ReadChars( * Capture the number of bytes actually consumed in dstRead. */ - dstWrote = dstRead = dstDecoded; + dstWrote = dstLimit; + dstRead = dstDecoded; TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); if (dstRead < dstDecoded) { @@ -8852,14 +8853,11 @@ CopyAndTranslateBuffer( } bufPtr = statePtr->inQueueHead; bytesInBuffer = BytesLeft(bufPtr); + if (bytesInBuffer == 0) { + return 0; + } copied = space; - if (bytesInBuffer <= copied) { - copied = bytesInBuffer; - } - if (copied == 0) { - return copied; - } TranslateInputEOL(statePtr, result, RemovePoint(bufPtr), &copied, &bytesInBuffer); bufPtr->nextRemoved += bytesInBuffer; -- cgit v0.12 From 62268820f73d797eebfc2a66ed3fa856c27daeb7 Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 10 Mar 2014 03:09:03 +0000 Subject: TranslateInputEOL doesn't need to return anything. No caller cares. Other optimizations and simplifications. --- generic/tclIO.c | 79 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index d23ca03..6dfdd03 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -214,7 +214,7 @@ static int StackSetBlockMode(Channel *chanPtr, int mode); static int SetBlockMode(Tcl_Interp *interp, Channel *chanPtr, int mode); static void StopCopy(CopyState *csPtr); -static int TranslateInputEOL(ChannelState *statePtr, char *dst, +static void TranslateInputEOL(ChannelState *statePtr, char *dst, const char *src, int *dstLenPtr, int *srcLenPtr); static void UpdateInterest(Channel *chanPtr); static int Write(Channel *chanPtr, const char *src, @@ -5574,7 +5574,7 @@ ReadChars( *--------------------------------------------------------------------------- */ -static int +static void TranslateInputEOL( ChannelState *statePtr, /* Channel being read, for EOL translation and * EOF character. */ @@ -5594,6 +5594,29 @@ TranslateInputEOL( int srcLen = *srcLenPtr; int inEofChar = statePtr->inEofChar; + /* + * Depending on the translation mode in use, there's no need + * to scan more srcLen bytes at srcStart than can possibly transform + * to dstLen bytes. This keeps the scan for eof char below from + * being pointlessly long. + */ + + switch (statePtr->inputTranslation) { + case TCL_TRANSLATE_LF: + case TCL_TRANSLATE_CR: + if (srcLen > dstLen) { + /* In these modes, each src byte become a dst byte. */ + srcLen = dstLen; + } + break; + default: + /* In other modes, at most 2 src bytes become a dst byte. */ + if (srcLen > 2 * dstLen) { + srcLen = 2 * dstLen; + } + break; + } + if (inEofChar != '\0') { /* * Make sure we do not read past any logical end of channel input @@ -5605,36 +5628,29 @@ TranslateInputEOL( } } - if (dstLen > srcLen) { - dstLen = srcLen; - } - switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: + case TCL_TRANSLATE_CR: if (dstStart != srcStart) { - memcpy(dstStart, srcStart, (size_t) dstLen); + memcpy(dstStart, srcStart, (size_t) srcLen); } - srcLen = dstLen; - break; - case TCL_TRANSLATE_CR: { - char *dst, *dstEnd; + if (statePtr->inputTranslation == TCL_TRANSLATE_CR) { + char *dst = dstStart; + char *dstEnd = dstStart + srcLen; - if (dstStart != srcStart) { - memcpy(dstStart, srcStart, (size_t) dstLen); - } - dstEnd = dstStart + dstLen; - for (dst = dstStart; dst < dstEnd; dst++) { - if (*dst == '\r') { - *dst = '\n'; + while ((dst = memchr(dst, '\r', dstEnd - dst))) { + *dst++ = '\n'; } } - srcLen = dstLen; + dstLen = srcLen; break; - } case TCL_TRANSLATE_CRLF: { char *dst; const char *src, *srcEnd, *srcMax; + if (dstLen > srcLen) { + dstLen = srcLen; + } dst = dstStart; src = srcStart; srcEnd = srcStart + dstLen; @@ -5660,29 +5676,23 @@ TranslateInputEOL( break; } case TCL_TRANSLATE_AUTO: { - char *dst; - const char *src, *srcEnd, *srcMax; + const char *srcEnd = srcStart + srcLen; + const char *dstEnd = dstStart + dstLen; + const char *src = srcStart; + char *dst = dstStart; - dst = dstStart; - src = srcStart; - srcEnd = srcStart + dstLen; - srcMax = srcStart + srcLen; - - if ((statePtr->flags & INPUT_SAW_CR) && (src < srcMax)) { + if ((statePtr->flags & INPUT_SAW_CR) && srcLen) { if (*src == '\n') { src++; } ResetFlag(statePtr, INPUT_SAW_CR); } - for ( ; src < srcEnd; ) { + for ( ; dst < dstEnd && src < srcEnd; ) { if (*src == '\r') { src++; - if (src >= srcMax) { + if (src == srcEnd) { SetFlag(statePtr, INPUT_SAW_CR); } else if (*src == '\n') { - if (srcEnd < srcMax) { - srcEnd++; - } src++; } *dst++ = '\n'; @@ -5710,10 +5720,7 @@ TranslateInputEOL( SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; ResetFlag(statePtr, INPUT_SAW_CR); - return 1; } - - return 0; } /* -- cgit v0.12 From 091096d315755aa89f28bd063b426e16a4c16e51 Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 10 Mar 2014 17:58:32 +0000 Subject: Bring CRLF translation in parallel with others. --- generic/tclIO.c | 20 +++++++------------- tests/io.test | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 6dfdd03..2971838 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5645,21 +5645,15 @@ TranslateInputEOL( dstLen = srcLen; break; case TCL_TRANSLATE_CRLF: { - char *dst; - const char *src, *srcEnd, *srcMax; - - if (dstLen > srcLen) { - dstLen = srcLen; - } - dst = dstStart; - src = srcStart; - srcEnd = srcStart + dstLen; - srcMax = srcStart + srcLen; + const char *srcEnd = srcStart + srcLen; + const char *dstEnd = dstStart + dstLen; + const char *src = srcStart; + char *dst = dstStart; - for ( ; src < srcEnd; ) { + for ( ; dst < dstEnd && src < srcEnd; ) { if (*src == '\r') { src++; - if (src >= srcMax) { + if (src == srcEnd) { src--; break; } else if (*src == '\n') { @@ -5710,7 +5704,7 @@ TranslateInputEOL( *dstLenPtr = dstLen; *srcLenPtr = srcLen; - if ((eof != NULL) && (srcStart + srcLen >= eof)) { + if (srcStart + srcLen == eof) { /* * EOF character was seen in EOL translated range. Leave current file * position pointing at the EOF character, but don't store the EOF diff --git a/tests/io.test b/tests/io.test index c325809..e3fff32 100644 --- a/tests/io.test +++ b/tests/io.test @@ -6858,6 +6858,20 @@ test io-52.14 {coverage of -translation crlf} { close $out file size $path(test2) } 29 +test io-52.14.1 {coverage of -translation crlf} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -buffersize 8 -translation crlf + set out [open $path(test2) w] + fcopy $in $out -size 2 + close $in + close $out + file size $path(test2) +} 2 test io-52.15 {coverage of -translation crlf} { file delete $path(test1) $path(test2) set out [open $path(test1) wb] -- cgit v0.12 From dd5ac1c6419faed6fedef71a19409cb52335353c Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 10 Mar 2014 19:00:19 +0000 Subject: Rewrite CRLF translation to use more system calls. --- generic/tclIO.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 2971838..1070f0a 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5645,28 +5645,38 @@ TranslateInputEOL( dstLen = srcLen; break; case TCL_TRANSLATE_CRLF: { - const char *srcEnd = srcStart + srcLen; - const char *dstEnd = dstStart + dstLen; - const char *src = srcStart; + const char *crFound, *src = srcStart; char *dst = dstStart; - - for ( ; dst < dstEnd && src < srcEnd; ) { - if (*src == '\r') { - src++; - if (src == srcEnd) { - src--; - break; - } else if (*src == '\n') { - *dst++ = *src++; - } else { - *dst++ = '\r'; - } + int lesser = (dstLen < srcLen) ? dstLen : srcLen; + + while ((crFound = memchr(src, '\r', lesser))) { + int numBytes = crFound - src; + memmove(dst, src, numBytes); + + dst += numBytes; + src += numBytes; + dstLen -= numBytes; + srcLen -= numBytes; + if (srcLen == 1) { + /* valid src bytes end in \r */ + lesser = 0; + break; + } + if (src[1] == '\n') { + *dst++ = '\n'; + srcLen -= 2; + src += 2; } else { - *dst++ = *src++; + *dst++ = '\r'; + srcLen--; + src++; } + dstLen++; + lesser = (dstLen < srcLen) ? dstLen : srcLen; } - srcLen = src - srcStart; - dstLen = dst - dstStart; + memmove(dst, src, lesser); + srcLen = src + lesser - srcStart; + dstLen = dst + lesser - dstStart; break; } case TCL_TRANSLATE_AUTO: { -- cgit v0.12 From ea4c5e97e3d2d2751578fa19df54d98988aa46f4 Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 10 Mar 2014 19:29:54 +0000 Subject: Test for the bug I just committed. --- tests/io.test | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/io.test b/tests/io.test index e3fff32..1bc3799 100644 --- a/tests/io.test +++ b/tests/io.test @@ -6872,6 +6872,20 @@ test io-52.14.1 {coverage of -translation crlf} { close $out file size $path(test2) } 2 +test io-52.14.2 {coverage of -translation crlf} { + file delete $path(test1) $path(test2) + set out [open $path(test1) wb] + chan configure $out -translation lf + puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz + close $out + set in [open $path(test1)] + chan configure $in -translation crlf + set out [open $path(test2) w] + fcopy $in $out -size 9 + close $in + close $out + file size $path(test2) +} 9 test io-52.15 {coverage of -translation crlf} { file delete $path(test1) $path(test2) set out [open $path(test1) wb] -- cgit v0.12 From d539d0925f6d60f1334d053247c8f3112e8de938 Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 10 Mar 2014 19:30:22 +0000 Subject: .... and then the bug fix. --- generic/tclIO.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 1070f0a..6194637 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5671,7 +5671,7 @@ TranslateInputEOL( srcLen--; src++; } - dstLen++; + dstLen--; lesser = (dstLen < srcLen) ? dstLen : srcLen; } memmove(dst, src, lesser); -- cgit v0.12 From 507194e18d3ee09110002c002daa35eea2b249fd Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 11 Mar 2014 03:38:55 +0000 Subject: Trial rewrite of AUTO input translation. --- generic/tclIO.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index 6194637..8d8e30f 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5680,6 +5680,40 @@ TranslateInputEOL( break; } case TCL_TRANSLATE_AUTO: { +#if 1 + const char *crFound, *src = srcStart; + char *dst = dstStart; + int lesser; + + if ((statePtr->flags & INPUT_SAW_CR) && srcLen) { + if (*src == '\n') { + src++; + srcLen--; + } + ResetFlag(statePtr, INPUT_SAW_CR); + } + lesser = (dstLen < srcLen) ? dstLen : srcLen; + while ((crFound = memchr(src, '\r', lesser))) { + int numBytes = crFound - src; + memmove(dst, src, numBytes); + + dst[numBytes] = '\n'; + dst += numBytes + 1; + dstLen -= numBytes + 1; + src += numBytes + 1; + srcLen -= numBytes + 1; + if (srcLen == 0) { + SetFlag(statePtr, INPUT_SAW_CR); + } else if (*src == '\n') { + src++; + srcLen--; + } + lesser = (dstLen < srcLen) ? dstLen : srcLen; + } + memmove(dst, src, lesser); + srcLen = src + lesser - srcStart; + dstLen = dst + lesser - dstStart; +#else const char *srcEnd = srcStart + srcLen; const char *dstEnd = dstStart + dstLen; const char *src = srcStart; @@ -5706,6 +5740,7 @@ TranslateInputEOL( } srcLen = src - srcStart; dstLen = dst - dstStart; +#endif break; } default: -- cgit v0.12 From 63b89595467a04db5b8a034eab47617e86ab6606 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 11 Mar 2014 16:51:38 +0000 Subject: Compress code for better single screen viewing. --- generic/tclIO.c | 55 ++++++++----------------------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 8d8e30f..b4f1c0c 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5653,10 +5653,8 @@ TranslateInputEOL( int numBytes = crFound - src; memmove(dst, src, numBytes); - dst += numBytes; - src += numBytes; - dstLen -= numBytes; - srcLen -= numBytes; + dst += numBytes; dstLen -= numBytes; + src += numBytes; srcLen -= numBytes; if (srcLen == 1) { /* valid src bytes end in \r */ lesser = 0; @@ -5664,12 +5662,10 @@ TranslateInputEOL( } if (src[1] == '\n') { *dst++ = '\n'; - srcLen -= 2; - src += 2; + src += 2; srcLen -= 2; } else { *dst++ = '\r'; - srcLen--; - src++; + src++; srcLen--; } dstLen--; lesser = (dstLen < srcLen) ? dstLen : srcLen; @@ -5680,16 +5676,12 @@ TranslateInputEOL( break; } case TCL_TRANSLATE_AUTO: { -#if 1 const char *crFound, *src = srcStart; char *dst = dstStart; int lesser; if ((statePtr->flags & INPUT_SAW_CR) && srcLen) { - if (*src == '\n') { - src++; - srcLen--; - } + if (*src == '\n') { src++; srcLen--; } ResetFlag(statePtr, INPUT_SAW_CR); } lesser = (dstLen < srcLen) ? dstLen : srcLen; @@ -5698,49 +5690,18 @@ TranslateInputEOL( memmove(dst, src, numBytes); dst[numBytes] = '\n'; - dst += numBytes + 1; - dstLen -= numBytes + 1; - src += numBytes + 1; - srcLen -= numBytes + 1; + dst += numBytes + 1; dstLen -= numBytes + 1; + src += numBytes + 1; srcLen -= numBytes + 1; if (srcLen == 0) { SetFlag(statePtr, INPUT_SAW_CR); } else if (*src == '\n') { - src++; - srcLen--; + src++; srcLen--; } lesser = (dstLen < srcLen) ? dstLen : srcLen; } memmove(dst, src, lesser); srcLen = src + lesser - srcStart; dstLen = dst + lesser - dstStart; -#else - const char *srcEnd = srcStart + srcLen; - const char *dstEnd = dstStart + dstLen; - const char *src = srcStart; - char *dst = dstStart; - - if ((statePtr->flags & INPUT_SAW_CR) && srcLen) { - if (*src == '\n') { - src++; - } - ResetFlag(statePtr, INPUT_SAW_CR); - } - for ( ; dst < dstEnd && src < srcEnd; ) { - if (*src == '\r') { - src++; - if (src == srcEnd) { - SetFlag(statePtr, INPUT_SAW_CR); - } else if (*src == '\n') { - src++; - } - *dst++ = '\n'; - } else { - *dst++ = *src++; - } - } - srcLen = src - srcStart; - dstLen = dst - dstStart; -#endif break; } default: -- cgit v0.12 From b16e407595d059711eecc4f8a0a62a18294edff0 Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 17 Mar 2014 17:56:47 +0000 Subject: Remove long dead "BAD_BLOCKING" support code so it no longer confuses people reading/editing this code. --- generic/tclIO.c | 133 +++++--------------------------------------------------- generic/tclIO.h | 23 ---------- 2 files changed, 10 insertions(+), 146 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index b4f1c0c..0f894e4 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4873,42 +4873,18 @@ Tcl_ReadRaw( ResetFlag(statePtr, CHANNEL_BLOCKED); } -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING /* - * [Bug 943274]. Better emulation of non-blocking channels for - * channels without BlockModeProc, by keeping track of true - * fileevents generated by the OS == Data waiting and reading if - * and only if we are sure to have data. + * Now go to the driver to get as much as is possible to + * fill the remaining request. Do all the error handling by + * ourselves. The code was stolen from 'GetInput' and + * slightly adapted (different return value here). + * + * The case of 'bytesToRead == 0' at this point cannot + * happen. */ - if ((statePtr->flags & CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !(statePtr->flags & CHANNEL_HAS_MORE_DATA)) { - /* - * We bypass the driver; it would block as no data is - * available. - */ - - nread = -1; - result = EWOULDBLOCK; - } else { -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - - /* - * Now go to the driver to get as much as is possible to fill - * the remaining request. Do all the error handling by - * ourselves. The code was stolen from 'GetInput' and slightly - * adapted (different return value here). - * - * The case of 'bytesToRead == 0' at this point cannot happen. - */ - - nread = ChanRead(chanPtr, bufPtr + copied, - bytesToRead - copied, &result); - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ + nread = ChanRead(chanPtr, bufPtr + copied, + bytesToRead - copied, &result); if (nread > 0) { /* @@ -4921,18 +4897,6 @@ Tcl_ReadRaw( if (nread < (bytesToRead - copied)) { SetFlag(statePtr, CHANNEL_BLOCKED); } - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - if (nread <= (bytesToRead - copied)) { - /* - * [Bug 943274] We have read the available data, clear - * flag. - */ - - ResetFlag(statePtr, CHANNEL_HAS_MORE_DATA); - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - } else if (nread == 0) { SetFlag(statePtr, CHANNEL_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; @@ -6041,32 +6005,7 @@ GetInput( return 0; } -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - /* - * [SF Tcl Bug 943274]. Better emulation of non-blocking channels for - * channels without BlockModeProc, by keeping track of true fileevents - * generated by the OS == Data waiting and reading if and only if we are - * sure to have data. - */ - - if ((statePtr->flags & CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !(statePtr->flags & CHANNEL_HAS_MORE_DATA)) { - /* - * Bypass the driver, it would block, as no data is available - */ - - nread = -1; - result = EWOULDBLOCK; - } else { -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - - nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead, &result); - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - + nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead, &result); if (nread > 0) { bufPtr->nextAdded += nread; @@ -6080,18 +6019,6 @@ GetInput( if (nread < toRead) { SetFlag(statePtr, CHANNEL_BLOCKED); } - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - if (nread <= toRead) { - /* - * [SF Tcl Bug 943274] We have read the available data, clear - * flag. - */ - - ResetFlag(statePtr, CHANNEL_HAS_MORE_DATA); - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - } else if (nread == 0) { SetFlag(statePtr, CHANNEL_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; @@ -7548,21 +7475,6 @@ Tcl_NotifyChannel( Channel *upChanPtr; const Tcl_ChannelType *upTypePtr; -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - /* - * [SF Tcl Bug 943274] For a non-blocking channel without blockmodeproc we - * keep track of actual input coming from the OS so that we can do a - * credible imitation of non-blocking behaviour. - */ - - if ((mask & TCL_READABLE) && - (statePtr->flags & CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !(statePtr->flags & CHANNEL_TIMER_FEV)) { - SetFlag(statePtr, CHANNEL_HAS_MORE_DATA); - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - /* * In contrast to the other API functions this procedure walks towards the * top of a stack and not down from it. @@ -7797,29 +7709,8 @@ ChannelTimerProc( */ statePtr->timer = Tcl_CreateTimerHandler(0, ChannelTimerProc,chanPtr); - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - /* - * Set the TIMER flag to notify the higher levels that the driver - * might have no data for us. We do this only if we are in - * non-blocking mode and the driver has no BlockModeProc because only - * then we really don't know if the driver will block or not. A - * similar test is done in "PeekAhead". - */ - - if ((statePtr->flags & CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL)) { - SetFlag(statePtr, CHANNEL_TIMER_FEV); - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - Tcl_Preserve(statePtr); Tcl_NotifyChannel((Tcl_Channel)chanPtr, TCL_READABLE); - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - ResetFlag(statePtr, CHANNEL_TIMER_FEV); -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - Tcl_Release(statePtr); } else { statePtr->timer = NULL; @@ -10381,10 +10272,6 @@ DumpFlags( ChanFlag('/', INPUT_SAW_CR); ChanFlag('D', CHANNEL_DEAD); ChanFlag('R', CHANNEL_RAW_MODE); -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - ChanFlag('T', CHANNEL_TIMER_FEV); - ChanFlag('H', CHANNEL_HAS_MORE_DATA); -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ ChanFlag('x', CHANNEL_INCLOSE); buf[i] ='\0'; diff --git a/generic/tclIO.h b/generic/tclIO.h index a57d4c5..59754cf 100644 --- a/generic/tclIO.h +++ b/generic/tclIO.h @@ -271,29 +271,6 @@ typedef struct ChannelState { * changes. */ #define CHANNEL_RAW_MODE (1<<16) /* When set, notes that the Raw API is * being used. */ -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING -#define CHANNEL_TIMER_FEV (1<<17) /* When set the event we are notified - * by is a fileevent generated by a - * timer. We don't know if the driver - * has more data and should not try to - * read from it. If the system needs - * more than is in the buffers out - * read routines will simulate a short - * read (0 characters read) */ -#define CHANNEL_HAS_MORE_DATA (1<<18) /* Set by NotifyChannel for a channel - * if and only if the channel is - * configured non-blocking, the driver - * for said channel has no - * blockmodeproc, and data has arrived - * for reading at the OS level). A - * GetInput will pass reading from the - * driver if the channel is - * non-blocking, without blockmode - * proc and the flag has not been set. - * A read will be performed if the - * flag is set. This will reset the - * flag as well. */ -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ #define CHANNEL_INCLOSE (1<<19) /* Channel is currently being closed. * Its structures are still live and -- cgit v0.12 From 69b5edf8708c05f4abdb039c64ea28932478b400 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 19 Mar 2014 20:32:37 +0000 Subject: Complete rewrite of DoRead(). --- generic/tclIO.c | 288 ++++++++++++++++++++++++++------------------------------ 1 file changed, 132 insertions(+), 156 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 0f894e4..2d22942 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -11,6 +11,7 @@ * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ +#undef NDEBUG #include "tclInt.h" #include "tclIO.h" #include @@ -173,8 +174,6 @@ static void CleanupChannelHandlers(Tcl_Interp *interp, static int CloseChannel(Tcl_Interp *interp, Channel *chanPtr, int errorCode); static void CommonGetsCleanup(Channel *chanPtr); -static int CopyAndTranslateBuffer(ChannelState *statePtr, - char *result, int space); static int CopyBuffer(Channel *chanPtr, char *result, int space); static int CopyData(CopyState *csPtr, int mask); static void CopyEventProc(ClientData clientData, int mask); @@ -188,7 +187,7 @@ static int DetachChannel(Tcl_Interp *interp, Tcl_Channel chan); static void DiscardInputQueued(ChannelState *statePtr, int discardSavedBuffers); static void DiscardOutputQueued(ChannelState *chanPtr); -static int DoRead(Channel *chanPtr, char *srcPtr, int slen); +static int DoRead(Channel *chanPtr, char *dst, int bytesToRead); static int DoReadChars(Channel *chan, Tcl_Obj *objPtr, int toRead, int appendFlag); static int FilterInputBytes(Channel *chanPtr, @@ -5363,7 +5362,7 @@ ReadChars( * record \r or \n yet. */ - assert(dstRead + 1 == dstDecoded); +// assert(dstRead + 1 == dstDecoded); assert(dst[dstRead] == '\r'); assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); @@ -5384,7 +5383,7 @@ ReadChars( assert(dstWrote == 0); assert(dstRead == 0); - assert(dstDecoded == 1); +// assert(dstDecoded == 1); /* * We decoded only the bare cr, and we cannot read a @@ -5882,6 +5881,9 @@ DiscardInputQueued( * * Reads input data from a device into a channel buffer. * + * IMPORTANT! This routine is only called on a chanPtr argument + * that is the top channel of a stack! + * * Results: * The return value is the Posix error code if an error occurred while * reading from the file, or 0 otherwise. @@ -8633,13 +8635,24 @@ CopyData( * * DoRead -- * - * Reads a given number of bytes from a channel. + * Stores up to "bytesToRead" bytes in memory pointed to by "dst". + * These bytes come from reading the channel "chanPtr" and + * performing the configured translations. * * No encoding conversions are applied to the bytes being read. * * Results: - * The number of characters read, or -1 on error. Use Tcl_GetErrno() to - * retrieve the error code for the error that occurred. + * The number of bytes actually stored (<= bytesToRead), + * or -1 if there is an error in reading the channel. Use + * Tcl_GetErrno() to retrieve the error code for the error + * that occurred. + * + * The number of bytes stored can be less than the number + * requested when + * - EOF is reached on the channel; or + * - the channel is non-blocking, and we've read all we can + * without blocking. + * - a channel reading error occurs (and we return -1) * * Side effects: * May cause input to be buffered. @@ -8650,186 +8663,149 @@ CopyData( static int DoRead( Channel *chanPtr, /* The channel from which to read. */ - char *bufPtr, /* Where to store input read. */ - int toRead) /* Maximum number of bytes to read. */ + char *dst, /* Where to store input read. */ + int bytesToRead) /* Maximum number of bytes to read. */ { ChannelState *statePtr = chanPtr->state; - /* State info for channel */ - int copied; /* How many characters were copied into the - * result string? */ - int copiedNow; /* How many characters were copied from the - * current input buffer? */ - int result; /* Of calling GetInput. */ + char *p = dst; - /* - * If we have not encountered a sticky EOF, clear the EOF bit. Either way - * clear the BLOCKED bit. We want to discover these anew during each - * operation. - */ + while (bytesToRead) { + /* + * Each pass through the loop is intended to process up to + * one channel buffer. + * + * First, if there is no full buffer, we attempt to + * create and/or fill one. + */ - if (!(statePtr->flags & CHANNEL_STICKY_EOF)) { - ResetFlag(statePtr, CHANNEL_EOF); - } - ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_NEED_MORE_DATA); + ChannelBuffer *bufPtr = statePtr->inQueueHead; - for (copied = 0; copied < toRead; copied += copiedNow) { - copiedNow = CopyAndTranslateBuffer(statePtr, bufPtr + copied, - toRead - copied); - if (copiedNow == 0) { - if (statePtr->flags & CHANNEL_EOF) { - goto done; - } - if (statePtr->flags & CHANNEL_BLOCKED) { - if (statePtr->flags & CHANNEL_NONBLOCKING) { - goto done; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); + if (statePtr->flags & CHANNEL_EOF + && (bufPtr == NULL || IsBufferEmpty(bufPtr))) { + break; + } + + while (bufPtr == NULL || !IsBufferFull(bufPtr)) { + int code; + + ResetFlag(statePtr, CHANNEL_BLOCKED); + moreData: + code = GetInput(chanPtr); + bufPtr = statePtr->inQueueHead; + if (statePtr->flags & (CHANNEL_EOF|CHANNEL_BLOCKED)) { + /* Further reads cannot do any more */ + break; } - result = GetInput(chanPtr); - if (result != 0) { - if (result != EAGAIN) { - copied = -1; - } - goto done; + + if (code) { + /* Read error */ + UpdateInterest(chanPtr); + return -1; } } - } - ResetFlag(statePtr, CHANNEL_BLOCKED); + /* Here we know bufPtr != NULL */ + int bytesRead = BytesLeft(bufPtr); + int bytesWritten = bytesToRead; - /* - * Update the notifier state so we don't block while there is still data - * in the buffers. - */ + if (bytesRead == 0 && statePtr->flags & CHANNEL_NONBLOCKING + && statePtr->flags & CHANNEL_BLOCKED) { + break; + } - done: - UpdateInterest(chanPtr); - return copied; -} - -/* - *---------------------------------------------------------------------- - * - * CopyAndTranslateBuffer -- - * - * Copy at most one buffer of input to the result space, doing eol - * translations according to mode in effect currently. - * - * Results: - * Number of bytes stored in the result buffer (as opposed to the number - * of bytes read from the channel). May return zero if no input is - * available to be translated. - * - * Side effects: - * Consumes buffered input. May deallocate one buffer. - * - *---------------------------------------------------------------------- - */ + TranslateInputEOL(statePtr, p, RemovePoint(bufPtr), + &bytesWritten, &bytesRead); + bufPtr->nextRemoved += bytesRead; + p += bytesWritten; + bytesToRead -= bytesWritten; -static int -CopyAndTranslateBuffer( - ChannelState *statePtr, /* Channel state from which to read input. */ - char *result, /* Where to store the copied input. */ - int space) /* How many bytes are available in result to - * store the copied input? */ -{ - ChannelBuffer *bufPtr; /* The buffer from which to copy bytes. */ - int bytesInBuffer; /* How many bytes are available to be copied - * in the current input buffer? */ - int copied; /* How many characters were already copied - * into the destination space? */ + if (!IsBufferEmpty(bufPtr)) { + /* + * Buffer is not empty. How can that be? + * + * 0) We stopped early because we got all the bytes + * we were seeking. That's fine. + */ - /* - * If there is no input at all, return zero. The invariant is that either - * there is no buffer in the queue, or if the first buffer is empty, it is - * also the last buffer (and thus there is no input in the queue). Note - * also that if the buffer is empty, we leave it in the queue. - */ + if (bytesToRead == 0) { + UpdateInterest(chanPtr); + break; + } - if (statePtr->inQueueHead == NULL) { - return 0; - } - bufPtr = statePtr->inQueueHead; - bytesInBuffer = BytesLeft(bufPtr); - if (bytesInBuffer == 0) { - return 0; - } + /* + * 1) We're @EOF because we saw eof char. + */ - copied = space; - TranslateInputEOL(statePtr, result, RemovePoint(bufPtr), - &copied, &bytesInBuffer); - bufPtr->nextRemoved += bytesInBuffer; + if (statePtr->inEofChar + && RemovePoint(bufPtr)[0] == statePtr->inEofChar) { + UpdateInterest(chanPtr); + break; + } - /* - * If the current buffer is empty recycle it. - */ + /* + * 2) The buffer holds a \r while in CRLF translation, followed + * by either the end of the buffer, or the eof char. + */ - if (IsBufferEmpty(bufPtr)) { - statePtr->inQueueHead = bufPtr->nextPtr; - if (statePtr->inQueueHead == NULL) { - statePtr->inQueueTail = NULL; - } - RecycleBuffer(statePtr, bufPtr, 0); - } else { + assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); + assert(RemovePoint(bufPtr)[0] == '\r'); - if (copied > 0) { - return copied; - } + if (BytesLeft(bufPtr) > 1) { - if (statePtr->inEofChar - && RemovePoint(bufPtr)[0] == statePtr->inEofChar) { - return 0; - } + /* TODO: shift this to TIEOL */ + assert(statePtr->inEofChar); + assert(RemovePoint(bufPtr)[1] == statePtr->inEofChar); - if (BytesLeft(bufPtr) == 1) { + bufPtr->nextRemoved++; + *p++ = '\r'; + bytesToRead--; + UpdateInterest(chanPtr); + break; + } - ChannelBuffer *nextPtr = bufPtr->nextPtr; + assert(BytesLeft(bufPtr) == 1); - if (nextPtr == NULL) { + if (bufPtr->nextPtr == NULL) { + /* There's no more buffered data.... */ if (statePtr->flags & CHANNEL_EOF) { - *result = '\r'; - bufPtr->nextRemoved += 1; - return 1; - } + /* ...and there never will be. */ - SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); - return 0; + *p++ = '\r'; + bytesToRead--; + bufPtr->nextRemoved++; + } else if (statePtr->flags & CHANNEL_BLOCKED) { + /* ...and we cannot get more now. */ + SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); + UpdateInterest(chanPtr); + break; + } else { + /* ... so we need to get some. */ + goto moreData; + } } - nextPtr->nextRemoved -= 1; - memcpy(RemovePoint(nextPtr), RemovePoint(bufPtr), 1); - RecycleBuffer(statePtr, bufPtr, 0); - statePtr->inQueueHead = nextPtr; - return 0; - } + if (bufPtr->nextPtr) { + /* There's a next buffer. Shift orphan \r to it. */ - if (statePtr->inEofChar - && RemovePoint(bufPtr)[1] == statePtr->inEofChar) { - *result = '\r'; - bufPtr->nextRemoved += 1; - return 1; + ChannelBuffer *nextPtr = bufPtr->nextPtr; + + nextPtr->nextRemoved -= 1; + RemovePoint(nextPtr)[0] = '\r'; + bufPtr->nextRemoved++; + } } - /* - * Buffer is not empty. How can that be? - * 0) We stopped early due to the value of "space". - * => copied > 0 and all is fine. - * 1) We saw eof char and stopped the translation copy. - * => if (copied > 0) or ((copied == 0) and @ eof char), - * return is fine. - * 2) The buffer holds a \r while in CRLF translation, followed - * by either the end of the buffer, or the eof char. - */ + if (IsBufferEmpty(bufPtr)) { + statePtr->inQueueHead = bufPtr->nextPtr; + if (statePtr->inQueueHead == NULL) { + statePtr->inQueueTail = NULL; + } + RecycleBuffer(statePtr, bufPtr, 0); + } } - /* - * Return the number of characters copied into the result buffer. This may - * be different from the number of bytes consumed, because of EOL - * translations. - */ - - return copied; + return (int)(p - dst); } /* -- cgit v0.12 From a174107efac416856e4183ea90821edac8c266b2 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 19 Mar 2014 21:43:31 +0000 Subject: Let TranslateInputEOL handle the "\r$eofChar" sequence in CRLF mode. --- generic/tclIO.c | 44 +++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 2d22942..267b659 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5362,7 +5362,7 @@ ReadChars( * record \r or \n yet. */ -// assert(dstRead + 1 == dstDecoded); + assert(dstRead + 1 == dstDecoded); assert(dst[dstRead] == '\r'); assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); @@ -5383,7 +5383,7 @@ ReadChars( assert(dstWrote == 0); assert(dstRead == 0); -// assert(dstDecoded == 1); + assert(dstDecoded == 1); /* * We decoded only the bare cr, and we cannot read a @@ -5620,10 +5620,14 @@ TranslateInputEOL( src += numBytes; srcLen -= numBytes; if (srcLen == 1) { /* valid src bytes end in \r */ - lesser = 0; - break; - } - if (src[1] == '\n') { + if (eof) { + *dst++ = '\r'; + src++; srcLen--; + } else { + lesser = 0; + break; + } + } else if (src[1] == '\n') { *dst++ = '\n'; src += 2; srcLen -= 2; } else { @@ -8708,11 +8712,6 @@ DoRead( int bytesRead = BytesLeft(bufPtr); int bytesWritten = bytesToRead; - if (bytesRead == 0 && statePtr->flags & CHANNEL_NONBLOCKING - && statePtr->flags & CHANNEL_BLOCKED) { - break; - } - TranslateInputEOL(statePtr, p, RemovePoint(bufPtr), &bytesWritten, &bytesRead); bufPtr->nextRemoved += bytesRead; @@ -8743,26 +8742,12 @@ DoRead( } /* - * 2) The buffer holds a \r while in CRLF translation, followed - * by either the end of the buffer, or the eof char. + * 2) The buffer holds a \r while in CRLF translation, + * followed by the end of the buffer. */ assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); assert(RemovePoint(bufPtr)[0] == '\r'); - - if (BytesLeft(bufPtr) > 1) { - - /* TODO: shift this to TIEOL */ - assert(statePtr->inEofChar); - assert(RemovePoint(bufPtr)[1] == statePtr->inEofChar); - - bufPtr->nextRemoved++; - *p++ = '\r'; - bytesToRead--; - UpdateInterest(chanPtr); - break; - } - assert(BytesLeft(bufPtr) == 1); if (bufPtr->nextPtr == NULL) { @@ -8803,6 +8788,11 @@ DoRead( } RecycleBuffer(statePtr, bufPtr, 0); } + + if (statePtr->flags & CHANNEL_NONBLOCKING + && statePtr->flags & CHANNEL_BLOCKED) { + break; + } } return (int)(p - dst); -- cgit v0.12 From eae6f97866efd02f09d961bf695047a3b47ac961 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 20 Mar 2014 16:31:11 +0000 Subject: Use assertions about the pushback buffers to simplify their handling. Mark several things left TODO. Some tidying. --- generic/tclIO.c | 60 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 267b659..b423bcc 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -6,6 +6,7 @@ * * Copyright (c) 1998-2000 Ajuba Solutions * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Contributions from Don Porter, NIST, 2014. (not subject to US copyright) * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -1679,17 +1680,17 @@ Tcl_StackChannel( */ if (((mask & TCL_READABLE) != 0) && (statePtr->inQueueHead != NULL)) { + /* - * Remark: It is possible that the channel buffers contain data from - * some earlier push-backs. + * When statePtr->inQueueHead is not NULL, we know + * prevChanPtr->inQueueHead must be NULL. */ - statePtr->inQueueTail->nextPtr = prevChanPtr->inQueueHead; - prevChanPtr->inQueueHead = statePtr->inQueueHead; + assert(prevChanPtr->inQueueHead == NULL); + assert(prevChanPtr->inQueueTail == NULL); - if (prevChanPtr->inQueueTail == NULL) { - prevChanPtr->inQueueTail = statePtr->inQueueTail; - } + prevChanPtr->inQueueHead = statePtr->inQueueHead; + prevChanPtr->inQueueTail = statePtr->inQueueTail; statePtr->inQueueHead = NULL; statePtr->inQueueTail = NULL; @@ -2254,6 +2255,7 @@ RecycleBuffer( } /* + * TODO * Only save buffers which are at least as big as the requested buffersize * for the channel. This is to honor dynamic changes of the buffersize * made by the user. @@ -3696,9 +3698,7 @@ Write( &statePtr->outputEncodingState, dst, dstLen + BUFFER_PADDING, &srcRead, &dstWrote, NULL); - if (srcRead != nlLen) { - Tcl_Panic("Can This Happen?"); - } + assert (srcRead == nlLen); bufPtr->nextAdded += dstWrote; src++; @@ -4837,6 +4837,7 @@ Tcl_ReadRaw( int nread, result, copied, copiedNow; /* + * TODO VERIFY * The check below does too much because it will reject a call to this * function with a channel which is part of an 'fcopy'. But we have to * allow this here or else the chaining in the transformation drivers will @@ -5925,16 +5926,11 @@ GetInput( * channel in the stack and use them. They can be the result of a * transformation which went away without reading all the information * placed in the area when it was stacked. - * - * Two possibilities for the state: No buffers in it, or a single empty - * buffer. In the latter case we can recycle it now. */ if (chanPtr->inQueueHead != NULL) { - if (statePtr->inQueueHead != NULL) { - RecycleBuffer(statePtr, statePtr->inQueueHead, 0); - statePtr->inQueueHead = NULL; - } + + assert(statePtr->inQueueHead == NULL); statePtr->inQueueHead = chanPtr->inQueueHead; statePtr->inQueueTail = chanPtr->inQueueTail; @@ -5962,6 +5958,7 @@ GetInput( statePtr->saveInBufPtr = NULL; /* + * TODO * Check the actual buffersize against the requested buffersize. * Buffers which are smaller than requested are squashed. This is done * to honor dynamic changes of the buffersize made by the user. @@ -5979,6 +5976,7 @@ GetInput( bufPtr->nextPtr = NULL; /* + * TODO * SF #427196: Use the actual size of the buffer to determine the * number of bytes to read from the channel and not the size for new * buffers. They can be different if the buffersize was changed @@ -6003,6 +6001,7 @@ GetInput( } /* + * TODO * If EOF is set, we should avoid calling the driver because on some * platforms it is impossible to read from a device after EOF. */ @@ -6524,6 +6523,7 @@ CheckChannelErrors( if (direction == TCL_READABLE) { /* + * TODO * If we have not encountered a sticky EOF, clear the EOF bit (sticky * EOF is set if we have seen the input eofChar, to prevent reading * beyond the eofChar). Also, always clear the BLOCKED bit. We want to @@ -6739,6 +6739,7 @@ Tcl_SetChannelBufferSize( ChannelState *statePtr; /* State of real channel structure. */ /* + * TODO * Clip the buffer size to force it into the [1,1M] range */ @@ -7375,6 +7376,7 @@ Tcl_SetChannelOption( } /* + * TODO * If bufsize changes, need to get rid of old utility buffer. */ @@ -8677,18 +8679,23 @@ DoRead( /* * Each pass through the loop is intended to process up to * one channel buffer. - * - * First, if there is no full buffer, we attempt to - * create and/or fill one. */ + int bytesRead, bytesWritten; ChannelBuffer *bufPtr = statePtr->inQueueHead; + /* + * When there's no buffered data to read, and we're at EOF, + * escape to the caller. + */ + if (statePtr->flags & CHANNEL_EOF && (bufPtr == NULL || IsBufferEmpty(bufPtr))) { break; } + /* If there is no full buffer, attempt to create and/or fill one. */ + while (bufPtr == NULL || !IsBufferFull(bufPtr)) { int code; @@ -8696,6 +8703,9 @@ DoRead( moreData: code = GetInput(chanPtr); bufPtr = statePtr->inQueueHead; + + assert (bufPtr != NULL); + if (statePtr->flags & (CHANNEL_EOF|CHANNEL_BLOCKED)) { /* Further reads cannot do any more */ break; @@ -8706,11 +8716,14 @@ DoRead( UpdateInterest(chanPtr); return -1; } + + assert (IsBufferFull(bufPtr)); } - /* Here we know bufPtr != NULL */ - int bytesRead = BytesLeft(bufPtr); - int bytesWritten = bytesToRead; + assert (bufPtr != NULL); + + bytesRead = BytesLeft(bufPtr); + bytesWritten = bytesToRead; TranslateInputEOL(statePtr, p, RemovePoint(bufPtr), &bytesWritten, &bytesRead); @@ -10155,6 +10168,7 @@ SetChannelFromAny( } if (objPtr->typePtr == &chanObjType) { /* + * TODO: TAINT Flag and dup'd channel values? * The channel is valid until any call to DetachChannel occurs. * Ensure consistency checks are done. */ -- cgit v0.12 From 8c3da02c3e41f1b7e029ed7633e250646fe7ec82 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 20 Mar 2014 19:25:11 +0000 Subject: Stop routine clearing of CHANNEL_EOF. Only clear when there's a reason (seek, eofchar change, ungets). Otherwise, once you hit EOF you stay there. --- generic/tclIO.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index b423bcc..1a56811 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4837,7 +4837,6 @@ Tcl_ReadRaw( int nread, result, copied, copiedNow; /* - * TODO VERIFY * The check below does too much because it will reject a call to this * function with a channel which is part of an 'fcopy'. But we have to * allow this here or else the chaining in the transformation drivers will @@ -5742,16 +5741,11 @@ Tcl_Ungets( statePtr->flags = flags; /* - * If we have encountered a sticky EOF, just punt without storing (sticky - * EOF is set if we have seen the input eofChar, to prevent reading beyond - * the eofChar). Otherwise, clear the EOF flags, and clear the BLOCKED - * bit. We want to discover these conditions anew in each operation. + * Clear the EOF flags, and clear the BLOCKED bit. */ - if (statePtr->flags & CHANNEL_STICKY_EOF) { - goto done; - } - ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_EOF); + ResetFlag(statePtr, + CHANNEL_BLOCKED | CHANNEL_STICKY_EOF | CHANNEL_EOF | INPUT_SAW_CR); bufPtr = AllocChannelBuffer(len); memcpy(InsertPoint(bufPtr), str, (size_t) len); @@ -6001,7 +5995,7 @@ GetInput( } /* - * TODO + * TODO - consider escape before buffer alloc * If EOF is set, we should avoid calling the driver because on some * platforms it is impossible to read from a device after EOF. */ @@ -6523,16 +6517,10 @@ CheckChannelErrors( if (direction == TCL_READABLE) { /* - * TODO - * If we have not encountered a sticky EOF, clear the EOF bit (sticky - * EOF is set if we have seen the input eofChar, to prevent reading - * beyond the eofChar). Also, always clear the BLOCKED bit. We want to - * discover these conditions anew in each operation. + * Clear the BLOCKED bit. We want to discover this condition + * anew in each operation. */ - if ((statePtr->flags & CHANNEL_STICKY_EOF) == 0) { - ResetFlag(statePtr, CHANNEL_EOF); - } ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_NEED_MORE_DATA); } -- cgit v0.12 From 60a84571795909d2b51dff06349107716ae3ab6d Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 20 Mar 2014 20:18:20 +0000 Subject: Don't allow buffer recycling to prevent or delay buffersize shrinkage. --- generic/tclIO.c | 68 ++++++++++++++++++++++++--------------------------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 1a56811..e7653f6 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2255,13 +2255,12 @@ RecycleBuffer( } /* - * TODO - * Only save buffers which are at least as big as the requested buffersize - * for the channel. This is to honor dynamic changes of the buffersize + * Only save buffers which have the requested buffersize for the + * channel. This is to honor dynamic changes of the buffersize * made by the user. */ - if ((bufPtr->bufLength - BUFFER_PADDING) < statePtr->bufSize) { + if ((bufPtr->bufLength - BUFFER_PADDING) != statePtr->bufSize) { ckfree((char *) bufPtr); return; } @@ -5952,14 +5951,13 @@ GetInput( statePtr->saveInBufPtr = NULL; /* - * TODO * Check the actual buffersize against the requested buffersize. - * Buffers which are smaller than requested are squashed. This is done + * Saved buffers of the wrong size are squashed. This is done * to honor dynamic changes of the buffersize made by the user. */ if ((bufPtr != NULL) - && (bufPtr->bufLength - BUFFER_PADDING < statePtr->bufSize)) { + && (bufPtr->bufLength - BUFFER_PADDING != statePtr->bufSize)) { ckfree((char *) bufPtr); bufPtr = NULL; } @@ -5969,22 +5967,8 @@ GetInput( } bufPtr->nextPtr = NULL; - /* - * TODO - * SF #427196: Use the actual size of the buffer to determine the - * number of bytes to read from the channel and not the size for new - * buffers. They can be different if the buffersize was changed - * between reads. - * - * Note: This affects performance negatively if the buffersize was - * extended but this small buffer is reused for all subsequent reads. - * The system never uses buffers with the requested bigger size in - * that case. An adjunct patch could try and delete all unused buffers - * it encounters and which are smaller than the formally requested - * buffersize. - */ - toRead = SpaceLeft(bufPtr); + assert(toRead == statePtr->bufSize); if (statePtr->inQueueTail == NULL) { statePtr->inQueueHead = bufPtr; @@ -6727,7 +6711,6 @@ Tcl_SetChannelBufferSize( ChannelState *statePtr; /* State of real channel structure. */ /* - * TODO * Clip the buffer size to force it into the [1,1M] range */ @@ -6738,7 +6721,27 @@ Tcl_SetChannelBufferSize( } statePtr = ((Channel *) chan)->state; + + if (statePtr->bufSize == sz) { + return; + } statePtr->bufSize = sz; + + /* + * If bufsize changes, need to get rid of old utility buffer. + */ + + if (statePtr->saveInBufPtr != NULL) { + RecycleBuffer(statePtr, statePtr->saveInBufPtr, 1); + statePtr->saveInBufPtr = NULL; + } + if ((statePtr->inQueueHead != NULL) + && (statePtr->inQueueHead->nextPtr == NULL) + && IsBufferEmpty(statePtr->inQueueHead)) { + RecycleBuffer(statePtr, statePtr->inQueueHead, 1); + statePtr->inQueueHead = NULL; + statePtr->inQueueTail = NULL; + } } /* @@ -7172,6 +7175,7 @@ Tcl_SetChannelOption( return TCL_ERROR; } Tcl_SetChannelBufferSize(chan, newBufferSize); + return TCL_OK; } else if (HaveOpt(2, "-encoding")) { Tcl_Encoding encoding; @@ -7202,6 +7206,7 @@ Tcl_SetChannelOption( statePtr->outputEncodingFlags = TCL_ENCODING_START; ResetFlag(statePtr, CHANNEL_NEED_MORE_DATA); UpdateInterest(chanPtr); + return TCL_OK; } else if (HaveOpt(2, "-eofchar")) { if (Tcl_SplitList(interp, newValue, &argc, &argv) == TCL_ERROR) { return TCL_ERROR; @@ -7363,23 +7368,6 @@ Tcl_SetChannelOption( return Tcl_BadChannelOption(interp, optionName, NULL); } - /* - * TODO - * If bufsize changes, need to get rid of old utility buffer. - */ - - if (statePtr->saveInBufPtr != NULL) { - RecycleBuffer(statePtr, statePtr->saveInBufPtr, 1); - statePtr->saveInBufPtr = NULL; - } - if ((statePtr->inQueueHead != NULL) - && (statePtr->inQueueHead->nextPtr == NULL) - && IsBufferEmpty(statePtr->inQueueHead)) { - RecycleBuffer(statePtr, statePtr->inQueueHead, 1); - statePtr->inQueueHead = NULL; - statePtr->inQueueTail = NULL; - } - return TCL_OK; } -- cgit v0.12 From 32fea5e38c09de7b9f9f19c93074ccdb8a6520d7 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 20 Mar 2014 23:21:48 +0000 Subject: Both callers of ChanRead() have simlar epilogs. Shift that into ChanRead and refactor. --- generic/tclIO.c | 123 +++++++++++++++++++++++--------------------------------- 1 file changed, 50 insertions(+), 73 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index e7653f6..18dab5a 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -348,15 +348,39 @@ static inline int ChanRead( Channel *chanPtr, char *dst, - int dstSize, - int *errnoPtr) + int dstSize) { + int bytesRead, result; + if (WillRead(chanPtr) < 0) { return -1; } - return chanPtr->typePtr->inputProc(chanPtr->instanceData, dst, dstSize, - errnoPtr); + bytesRead = chanPtr->typePtr->inputProc(chanPtr->instanceData, + dst, dstSize, &result); + + if (bytesRead > 0) { + /* + * If we get a short read, signal up that we may be BLOCKED. We should + * avoid calling the driver because on some platforms we will block in + * the low level reading code even though the channel is set into + * nonblocking mode. + */ + + if (bytesRead < dstSize) { + SetFlag(chanPtr->state, CHANNEL_BLOCKED); + } + } else if (bytesRead == 0) { + SetFlag(chanPtr->state, CHANNEL_EOF); + chanPtr->state->inputEncodingFlags |= TCL_ENCODING_END; + } else { /* bytesRead < 0 */ + if ((result == EWOULDBLOCK) || (result == EAGAIN)) { + SetFlag(chanPtr->state, CHANNEL_BLOCKED); + result = EAGAIN; + } + Tcl_SetErrno(result); + } + return bytesRead; } static inline Tcl_WideInt @@ -4833,7 +4857,7 @@ Tcl_ReadRaw( Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ - int nread, result, copied, copiedNow; + int nread, copied, copiedNow; /* * The check below does too much because it will reject a call to this @@ -4862,11 +4886,11 @@ Tcl_ReadRaw( bytesToRead - copied); if (copiedNow == 0) { if (statePtr->flags & CHANNEL_EOF) { - goto done; + break; } if (statePtr->flags & CHANNEL_BLOCKED) { if (statePtr->flags & CHANNEL_NONBLOCKING) { - goto done; + break; } ResetFlag(statePtr, CHANNEL_BLOCKED); } @@ -4881,48 +4905,21 @@ Tcl_ReadRaw( * happen. */ - nread = ChanRead(chanPtr, bufPtr + copied, - bytesToRead - copied, &result); - - if (nread > 0) { - /* - * If we get a short read, signal up that we may be BLOCKED. - * We should avoid calling the driver because on some - * platforms we will block in the low level reading code even - * though the channel is set into nonblocking mode. - */ - - if (nread < (bytesToRead - copied)) { - SetFlag(statePtr, CHANNEL_BLOCKED); - } - } else if (nread == 0) { - SetFlag(statePtr, CHANNEL_EOF); - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - - } else if (nread < 0) { - if ((result == EWOULDBLOCK) || (result == EAGAIN)) { - if (copied > 0) { - /* - * Information that was copied earlier has precedence - * over EAGAIN/WOULDBLOCK handling. - */ - - return copied; - } + nread = ChanRead(chanPtr, bufPtr+copied, bytesToRead-copied); - SetFlag(statePtr, CHANNEL_BLOCKED); - result = EAGAIN; + if (nread < 0) { + if (statePtr->flags & CHANNEL_BLOCKED && copied > 0) { + ResetFlag(statePtr, CHANNEL_BLOCKED); + break; } - - Tcl_SetErrno(result); return -1; } - return copied + nread; + copied += nread; + break; } } - done: return copied; } @@ -5019,7 +5016,7 @@ DoReadChars( ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; - int factor, copied, copiedNow, result; + int factor, copied, copiedNow; Tcl_Encoding encoding; int binaryMode; #define UTF_EXPANSION_FACTOR 1024 @@ -5090,9 +5087,8 @@ DoReadChars( } ResetFlag(statePtr, CHANNEL_BLOCKED); } - result = GetInput(chanPtr); - if (result != 0) { - if (result == EAGAIN) { + if (GetInput(chanPtr) != 0) { + if (statePtr->flags & CHANNEL_BLOCKED) { break; } copied = -1; @@ -5883,8 +5879,9 @@ DiscardInputQueued( * that is the top channel of a stack! * * Results: - * The return value is the Posix error code if an error occurred while - * reading from the file, or 0 otherwise. + * The return value is 0 to indicate success, or -1 if an error + * occurred while reading from the channel. Call Tcl_GetErrno() + * to get the Posix error code. * * Side effects: * Reads from the underlying device. @@ -5897,7 +5894,6 @@ GetInput( Channel *chanPtr) /* Channel to read input from. */ { int toRead; /* How much to read? */ - int result; /* Of calling driver. */ int nread; /* How much was read from channel? */ ChannelBuffer *bufPtr; /* New buffer to add to input queue. */ ChannelState *statePtr = chanPtr->state; @@ -5911,7 +5907,8 @@ GetInput( */ if (CheckForDeadChannel(NULL, statePtr)) { - return EINVAL; + Tcl_SetErrno(EINVAL); + return -1; } /* @@ -5988,31 +5985,11 @@ GetInput( return 0; } - nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead, &result); - if (nread > 0) { - bufPtr->nextAdded += nread; - - /* - * If we get a short read, signal up that we may be BLOCKED. We should - * avoid calling the driver because on some platforms we will block in - * the low level reading code even though the channel is set into - * nonblocking mode. - */ - - if (nread < toRead) { - SetFlag(statePtr, CHANNEL_BLOCKED); - } - } else if (nread == 0) { - SetFlag(statePtr, CHANNEL_EOF); - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - } else if (nread < 0) { - if ((result == EWOULDBLOCK) || (result == EAGAIN)) { - SetFlag(statePtr, CHANNEL_BLOCKED); - result = EAGAIN; - } - Tcl_SetErrno(result); - return result; + nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead); + if (nread < 0) { + return -1; } + bufPtr->nextAdded += nread; return 0; } -- cgit v0.12 From f27a277c1e1c06a9ddd1c93606a9d884c8d844d7 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Mar 2014 02:36:47 +0000 Subject: Documentation header for ChanRead() --- generic/tclIO.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 18dab5a..6e5dc05 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -342,9 +342,30 @@ static Tcl_ObjType chanObjType = { #define MAX_CHANNEL_BUFFER_SIZE (1024*1024) /* - * ChanRead, dropped here by a time traveler, see 8.6 + *--------------------------------------------------------------------------- + * + * ChanRead -- + * + * Read up to bytes using the inputProc of chanPtr, store them at dst, + * and return the number of bytes stored. + * + * Results: + * The return value of the driver inputProc, + * - number of bytes stored at dst, or + * - -1 on error, with a Posix error code available to the + * caller by calling Tcl_GetErrno(). + * + * Side effects: + * The CHANNEL_BLOCKED and CHANNEL_EOF flags of the channel + * state are set as appropriate. + * On EOF, the inputEncodingFlags are set to perform ending + * operations on decoding. + * TODO - Is this really the right place for that? + * + *--------------------------------------------------------------------------- */ -static inline int + +static int ChanRead( Channel *chanPtr, char *dst, -- cgit v0.12 From e21cda73652e2cf4a060a0411e779935b427a154 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Mar 2014 12:56:46 +0000 Subject: io-34.21 - fix bugs in normally skipped test. io-35.18b - knownBug is not buggy on this branch. --- tests/io.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/io.test b/tests/io.test index cedb337..7f1a357 100644 --- a/tests/io.test +++ b/tests/io.test @@ -41,7 +41,7 @@ testConstraint testthread [llength [info commands testthread]] # You need a *very* special environment to do some tests. In # particular, many file systems do not support large-files... -testConstraint largefileSupport 0 +testConstraint largefileSupport 1 # some tests can only be run is umask is 2 # if "umask" cannot be run, the tests will be skipped. @@ -4433,10 +4433,10 @@ test io-34.21 {Tcl_Seek and Tcl_Tell on large files} {largefileSupport} { puts -nonewline $f abcdef lappend l [tell $f] close $f - lappend l [file size $f] + lappend l [file size $path(test3)] # truncate... close [open $path(test3) w] - lappend l [file size $f] + lappend l [file size $path(test3)] set l } {0 6 6 4294967296 4294967302 4294967302 0} @@ -4729,7 +4729,7 @@ test io-35.18a {Tcl_Eof, eof char, cr write, crlf read} -body { close $f list $s $l $e [scan [string index $in end] %c] } -result {9 8 1 13} -test io-35.18b {Tcl_Eof, eof char, cr write, crlf read} -constraints knownBug -body { +test io-35.18b {Tcl_Eof, eof char, cr write, crlf read} -body { file delete $path(test1) set f [open $path(test1) w] fconfigure $f -translation cr -eofchar \x1a -- cgit v0.12 From 657b902a0e5afa5596833584329b653f7c0f277d Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Mar 2014 12:57:58 +0000 Subject: Fixup ChanRead() header. Note (dstSize > 0) precondition. --- generic/tclIO.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 6e5dc05..e7eaefa 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -346,8 +346,8 @@ static Tcl_ObjType chanObjType = { * * ChanRead -- * - * Read up to bytes using the inputProc of chanPtr, store them at dst, - * and return the number of bytes stored. + * Read up to dstsize bytes using the inputProc of chanPtr, store + * them at dst, and return the number of bytes stored. * * Results: * The return value of the driver inputProc, @@ -373,6 +373,12 @@ ChanRead( { int bytesRead, result; + /* + * If the caller asked for zero bytes, we'd force the inputProc + * to return zero bytes, and then misinterpret that as EOF + */ + assert(dstSize > 0); + if (WillRead(chanPtr) < 0) { return -1; } -- cgit v0.12 From 5bd5d7b876314f563f1ac021424b7f79f45196dd Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Mar 2014 12:58:29 +0000 Subject: Restore default suppression of large file test. --- tests/io.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/io.test b/tests/io.test index 7f1a357..bfe6051 100644 --- a/tests/io.test +++ b/tests/io.test @@ -41,7 +41,7 @@ testConstraint testthread [llength [info commands testthread]] # You need a *very* special environment to do some tests. In # particular, many file systems do not support large-files... -testConstraint largefileSupport 1 +testConstraint largefileSupport 0 # some tests can only be run is umask is 2 # if "umask" cannot be run, the tests will be skipped. -- cgit v0.12 From 6b106c4d93bfc4a452a3cf9d78aa3ee25761e553 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Mar 2014 13:20:06 +0000 Subject: Convert "impossible" test to a "knownBug" test. Exposes a segfault! --- tests/ioCmd.test | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/ioCmd.test b/tests/ioCmd.test index 768a748..8f0bfbf 100644 --- a/tests/ioCmd.test +++ b/tests/ioCmd.test @@ -1927,6 +1927,8 @@ test iocmd-32.0 {origin interpreter of moved channel gone} -match glob -body { test iocmd-32.1 {origin interpreter of moved channel destroyed during access} -match glob -body { + # This test segfaults; Ought to fix that. + set ida [interp create];#puts <<$ida>> set idb [interp create];#puts <<$idb>> @@ -1940,13 +1942,12 @@ test iocmd-32.1 {origin interpreter of moved channel destroyed during access} -m proc foo {args} { oninit; onfinal; track; # destroy interpreter during channel access - # Actually not possible for an interp to destroy itself. - interp delete {} - return} + suicide} set chan [chan create {r w} foo] fconfigure $chan -buffering none set chan }] + interp alias $ida suicide {} interp delete $ida # Move channel to 2nd thread. interp eval $ida [list testchannel cut $chan] @@ -1965,7 +1966,7 @@ test iocmd-32.1 {origin interpreter of moved channel destroyed during access} -m set res }] set res -} -constraints {testchannel impossible} \ +} -constraints {testchannel knownBug} \ -result {Owner lost} test iocmd-32.2 {delete interp of reflected chan} { -- cgit v0.12 From f715783b1bff0fe44a894ff26049debf8cb184f2 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Mar 2014 13:40:58 +0000 Subject: Added comment explaining the "knownBug" --- tests/iogt.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/iogt.test b/tests/iogt.test index 3882ecc..3b94747 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -916,6 +916,15 @@ test iogt-6.0 {Push back} testchannel { } {xxx} test iogt-6.1 {Push back and up} {testchannel knownBug} { + + # This test demonstrates the bug/misfeature in the stacked + # channel implementation that data can be discarded if it is + # read into the buffers of one channel in the stack, and then + # that channel is popped before anything above it reads. + # + # This bug can be worked around by always setting -buffersize + # to 1, but who wants to do that? + set f [open $path(dummy) r] # contents of dummy = "abcdefghi..." -- cgit v0.12 From 129e8e28a57eb14bc1a2c5e06dd60f90124ab2bf Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Mar 2014 14:07:40 +0000 Subject: Correct namespace bugs in normally skipped tests. Constrain them as "knownBug" rather than "unknownFailure". --- tests/iogt.test | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/iogt.test b/tests/iogt.test index 3b94747..0a25418 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -159,8 +159,8 @@ proc fevent {fdelay idelay blocks script data} { #puts stdout ">>>>>" ; flush stdout - uplevel #0 set sock $sk - set res [uplevel #0 $script] + uplevel 1 set sock $sk + set res [uplevel 1 $script] catch {close $sk} return $res @@ -634,7 +634,7 @@ delete/write {} *ignored*} test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} \ - {testchannel unknownFailure} { + {testchannel knownBug} { # This test to check the validity of aquired Tcl_Channel references is # not possible because even a backgrounded fcopy will immediately start # to copy data, without waiting for the event loop. This is done only in @@ -651,6 +651,7 @@ test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} \ set fin [open $path(dummy) r] fevent 1000 500 {20 20 20 10 1 1} { + variable copy close $fin set fout [open dummyout w] @@ -688,7 +689,7 @@ test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} \ } {1 {create/write create/read write flush/write flush/read delete/write delete/read}} -test iogt-4.0 {fileevent readable, after transform} {testchannel unknownFailure} { +test iogt-4.0 {fileevent readable, after transform} {testchannel knownBug} { set fin [open $path(dummy) r] set data [read $fin] close $fin @@ -718,10 +719,11 @@ test iogt-4.0 {fileevent readable, after transform} {testchannel unknownFailure} } fevent 1000 500 {20 20 20 10 1} { + variable stop audit_flow trail -attach $sock rblocks_t rbuf trail 23 -attach $sock - fileevent $sock readable [list Get $sock] + fileevent $sock readable [namespace code [list Get $sock]] flush $sock ; # now, or fcopy will error us out # But the 1 second delay should be enough to @@ -819,7 +821,7 @@ delete/write {} *ignored* delete/read {} *ignored*} ; # catch unescaped quote " -test iogt-5.0 {EOF simulation} {testchannel unknownFailure} { +test iogt-5.0 {EOF simulation} {testchannel knownBug} { set fin [open $path(dummy) r] set fout [open $path(dummyout) w] -- cgit v0.12 From 2266861ddbdc18e12918eb94efeefc37edc894f8 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 21 Mar 2014 14:45:00 +0000 Subject: missing declaration --- generic/tclIO.c | 1 + 1 file changed, 1 insertion(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index e7eaefa..a62f80e 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -165,6 +165,7 @@ typedef struct CloseCallback { static ChannelBuffer * AllocChannelBuffer(int length); static void ChannelTimerProc(ClientData clientData); +static int ChanRead(Channel *chanPtr, char *dst, int dstSize); static int CheckChannelErrors(ChannelState *statePtr, int direction); static int CheckForDeadChannel(Tcl_Interp *interp, -- cgit v0.12 From 870ed20799fc0d228ffcf6e7add98824b0182950 Mon Sep 17 00:00:00 2001 From: ferrieux Date: Mon, 24 Mar 2014 21:42:56 +0000 Subject: Add test io-53.12 to verify proper unbuffered sync-fcopy [Bug #3096275] --- tests/io.test | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/io.test b/tests/io.test index 19cd9a5..d3f249c 100644 --- a/tests/io.test +++ b/tests/io.test @@ -7450,6 +7450,25 @@ test io-53.11 {Bug 2895565} -setup { removeFile out removeFile in } -result {40 bytes copied} +test io-53.12 {CopyData: foreground short reads, aka bug 3096275} {stdio unix openpipe fcopy} { + file delete $path(pipe) + set f1 [open $path(pipe) w] + puts -nonewline $f1 { + fconfigure stdin -translation binary -blocking 0 + fconfigure stdout -buffering none -translation binary + fcopy stdin stdout + } + close $f1 + set f1 [open "|[list [interpreter] $path(pipe)]" r+] + fconfigure $f1 -translation binary -buffering none + puts -nonewline $f1 A + after 2000 {set ::done timeout} + fileevent $f1 readable {set ::done ok} + vwait ::done + set ch [read $f1 1] + close $f1 + list $::done $ch +} {ok A} test io-54.1 {Recursive channel events} {socket fileevent} { # This test checks to see if file events are delivered during recursive -- cgit v0.12 From 4b6ab47efdc71922068677ef074031e180f359d9 Mon Sep 17 00:00:00 2001 From: oehhar Date: Fri, 4 Apr 2014 16:27:39 +0000 Subject: Avoid multiple returns of connect errors --- win/tclWinSock.c | 146 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 43 deletions(-) diff --git a/win/tclWinSock.c b/win/tclWinSock.c index 036f3b9..ed9fa32 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -172,7 +172,7 @@ struct TcpState { struct addrinfo *addr; /* Iterator over addrlist. */ struct addrinfo *myaddrlist;/* Local address. */ struct addrinfo *myaddr; /* Iterator over myaddrlist. */ - int status; /* Cache status of async socket. */ + int error; /* Cache status of async socket. */ int cachedBlocking; /* Cache blocking mode of async socket. */ volatile int connectError; /* Async connect error set by notifier thread. * Set by notifier thread, access must be @@ -255,8 +255,7 @@ static LRESULT CALLBACK SocketProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static int SocketsEnabled(void); static void TcpAccept(TcpFdList *fds, SOCKET newSocket, address addr); -static int WaitForConnect(TcpState *statePtr, int *errorCodePtr, - int noblock); +static int WaitForConnect(TcpState *statePtr, int *errorCodePtr); static int WaitForSocketEvent(TcpState *statePtr, int events, int *errorCodePtr); static void AddSocketInfoFd(TcpState *statePtr, SOCKET socket); @@ -573,8 +572,13 @@ TcpBlockModeProc( static int WaitForConnect( TcpState *statePtr, /* State of the socket. */ - int *errorCodePtr, /* Where to store errors? */ - int noblock) /* Don't wait, even for sockets in blocking mode */ + int *errorCodePtr) /* Where to store errors? + * A passed null-pointer activates background mode. + * In this case, an eventual error is stored in + * statePtr->error. + * In addition, we do never block and allow a next + * processing cycle to happen. + */ { int result; int oldMode; @@ -605,10 +609,11 @@ WaitForConnect( statePtr->readyEvents &= ~(FD_CONNECT); /* - * For blocking sockets disable async connect - * as we continue now synchoneously + * For blocking sockets and foreground processing + * disable async connect as we continue now synchoneously */ - if ( !(noblock || (statePtr->flags & TCP_ASYNC_SOCKET)) ) { + if ( errorCodePtr != NULL && + ! (statePtr->flags & TCP_ASYNC_SOCKET) ) { CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); } @@ -624,13 +629,19 @@ WaitForConnect( /* Succesfully connected or async connect restarted */ if (result == TCL_OK) { if ( statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING ) { - *errorCodePtr = EWOULDBLOCK; + if (errorCodePtr != NULL) { + *errorCodePtr = EWOULDBLOCK; + } return -1; } return 0; } /* error case */ - *errorCodePtr = Tcl_GetErrno(); + if (errorCodePtr != NULL) { + *errorCodePtr = Tcl_GetErrno(); + } else { + statePtr->error = Tcl_GetErrno(); + } return -1; } @@ -641,7 +652,11 @@ WaitForConnect( * A non blocking socket waiting for an asyncronous connect * returns directly an error */ - if ( noblock || (statePtr->flags & TCP_ASYNC_SOCKET) ) { + if ( errorCodePtr == NULL ) { + /* Backround operation */ + return -1; + } else if (statePtr->flags & TCP_ASYNC_SOCKET) { + /* foreground operation but non blocking socket */ *errorCodePtr = EWOULDBLOCK; return -1; } @@ -715,7 +730,7 @@ TcpInputProc( * For a non blocking socket return EWOULDBLOCK if connect not terminated */ - if (WaitForConnect(statePtr, errorCodePtr, 0) != 0) { + if (WaitForConnect(statePtr, errorCodePtr) != 0) { return -1; } @@ -844,7 +859,7 @@ TcpOutputProc( * For a non blocking socket return EWOULDBLOCK if connect not terminated */ - if (WaitForConnect(statePtr, errorCodePtr, 0) != 0) { + if (WaitForConnect(statePtr, errorCodePtr) != 0) { return -1; } @@ -1184,7 +1199,7 @@ TcpGetOptionProc( char host[NI_MAXHOST], port[NI_MAXSERV]; SOCKET sock; size_t len = 0; - int errorCode; + int errorCode = 0; int reverseDNS = 0; #define SUPPRESS_RDNS_VAR "::tcl::unsupported::noReverseDNS" @@ -1202,8 +1217,11 @@ TcpGetOptionProc( return TCL_ERROR; } - /* Go one step in async connect */ - WaitForConnect(statePtr, &errorCode, 1); + /* + * Go one step in async connect + * If any error is thrown save it as backround error to report eventually below + */ + WaitForConnect(statePtr, NULL); sock = statePtr->sockets->fd; if (optionName != NULL) { @@ -1216,30 +1234,52 @@ TcpGetOptionProc( /* * Do not return any errors if async connect is running */ - if (! (statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING) ) { - int optlen; - int ret; - DWORD err; + if ( ! (statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING) ) { - /* - * Populater the err Variable with a possix error - */ - optlen = sizeof(int); - ret = TclWinGetSockOpt(sock, SOL_SOCKET, SO_ERROR, - (char *)&err, &optlen); - /* - * The error was not returned directly but should be - * taken from WSA - */ - if (ret == SOCKET_ERROR) { - err = WSAGetLastError(); - } - /* - * Return error message - */ - if (err) { - TclWinConvertError(err); - Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(Tcl_GetErrno()), -1); + + if ( statePtr->flags & TCP_ASYNC_CONNECT_FAILED ) { + + /* + * In case of a failed async connect, eventually report the + * connect error only once. + * Do not report the system error, as this comes again and again. + */ + + if ( statePtr->error != 0 ) { + Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(statePtr->error), -1); + statePtr->error = 0; + } + + } else { + + /* + * Report an eventual last error of the socket system + */ + + int optlen; + int ret; + DWORD err; + + /* + * Populater the err Variable with a possix error + */ + optlen = sizeof(int); + ret = TclWinGetSockOpt(sock, SOL_SOCKET, SO_ERROR, + (char *)&err, &optlen); + /* + * The error was not returned directly but should be + * taken from WSA + */ + if (ret == SOCKET_ERROR) { + err = WSAGetLastError(); + } + /* + * Return error message + */ + if (err) { + TclWinConvertError(err); + Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(Tcl_GetErrno()), -1); + } } } return TCL_OK; @@ -2555,16 +2595,36 @@ SocketEventProc( return 1; } + /* + * Clear flag that (this) event is pending + */ + statePtr->flags &= ~SOCKET_PENDING; - /* Continue async connect if pending and ready */ + /* + * Continue async connect if pending and ready + */ + if ( statePtr->readyEvents & FD_CONNECT ) { - statePtr->readyEvents &= ~(FD_CONNECT); if ( statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING ) { + + /* + * Do one step and save eventual connect error + */ + + SetEvent(tsdPtr->socketListLock); + WaitForConnect(statePtr,NULL); + + } else { + + /* + * No async connect reenter pending. Just clear event. + */ + + statePtr->readyEvents &= ~(FD_CONNECT); SetEvent(tsdPtr->socketListLock); - CreateClientSocket(NULL, statePtr); - return 1; } + return 1; } /* -- cgit v0.12 From 77113c6828286012fe17288e3132811cb24f6fe3 Mon Sep 17 00:00:00 2001 From: oehhar Date: Mon, 7 Apr 2014 12:34:18 +0000 Subject: Return async connect error by first following read or write operation. --- win/tclWinSock.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/win/tclWinSock.c b/win/tclWinSock.c index ed9fa32..b05dc32 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -585,11 +585,22 @@ WaitForConnect( ThreadSpecificData *tsdPtr; /* + * Check if an async connect error is not jet reported. + * If yes, report it now. + */ + + if ( errorCodePtr != NULL && statePtr->error != 0 ) { + *errorCodePtr = statePtr->error; + statePtr->error = 0; + return -1; + } + + /* * Check if an async connect is running. If not return ok */ if ( !(statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING) ) return 0; - + /* * Be sure to disable event servicing so we are truly modal. */ -- cgit v0.12 From 6ab79205ac7597fa0c7b84ef86c9e341b99bc8b6 Mon Sep 17 00:00:00 2001 From: oehhar Date: Mon, 7 Apr 2014 15:08:31 +0000 Subject: Renamed function CreateClientSocket to TcpConnect and variable error to connectError --- win/tclWinSock.c | 51 ++++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/win/tclWinSock.c b/win/tclWinSock.c index b05dc32..7593396 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -172,11 +172,10 @@ struct TcpState { struct addrinfo *addr; /* Iterator over addrlist. */ struct addrinfo *myaddrlist;/* Local address. */ struct addrinfo *myaddr; /* Iterator over myaddrlist. */ - int error; /* Cache status of async socket. */ + int connectError; /* Cache status of async socket. */ int cachedBlocking; /* Cache blocking mode of async socket. */ volatile int connectError; /* Async connect error set by notifier thread. - * Set by notifier thread, access must be - * protected by semaphore */ + * Access must be protected by semaphore */ struct TcpState *nextPtr; /* The next socket on the per-thread socket * list. */ }; @@ -193,7 +192,7 @@ struct TcpState { #define SOCKET_PENDING (1<<3) /* A message has been sent for this * socket */ #define TCP_ASYNC_CONNECT_REENTER_PENDING (1<<4) - /* CreateClientSocket was called to + /* TcpConnect was called to * process an async connect. This * flag indicates that reentry is * still pending */ @@ -246,7 +245,7 @@ static WNDCLASS windowClass; * Static routines for this file: */ -static int CreateClientSocket(Tcl_Interp *interp, +static int TcpConnect(Tcl_Interp *interp, TcpState *state); static void InitSockets(void); static TcpState * NewSocketInfo(SOCKET socket); @@ -553,10 +552,14 @@ TcpBlockModeProc( * * WaitForConnect -- * - * Wait for a connection on an asynchronously opened socket to be - * completed. In nonblocking mode, just test if the connection - * has completed without blocking. The noblock parameter allows to - * enforce nonblocking behaviour even on sockets in blocking mode. + * Check the state of an async connect process. If a connection + * attempt terminated, process it, which may finalize it or may + * start the next attempt. + * There are two modes of operation, defined by errorCodePtr: + * * non-NULL: Called by explicite read/write command. block if + * socket is blocking. Return a possible error and clear it. + * * Null: Called by a backround operation. Never block and + * save eventual error in statePtr->connectError. * * Results: * 0 if the connection has completed, -1 if still in progress @@ -574,9 +577,9 @@ WaitForConnect( TcpState *statePtr, /* State of the socket. */ int *errorCodePtr) /* Where to store errors? * A passed null-pointer activates background mode. - * In this case, an eventual error is stored in - * statePtr->error. - * In addition, we do never block and allow a next + * In this case, a possible error is stored in + * statePtr->connectError. + * In addition, we do never block and allow the next * processing cycle to happen. */ { @@ -589,15 +592,16 @@ WaitForConnect( * If yes, report it now. */ - if ( errorCodePtr != NULL && statePtr->error != 0 ) { - *errorCodePtr = statePtr->error; - statePtr->error = 0; + if ( errorCodePtr != NULL && statePtr->connectError != 0 ) { + *errorCodePtr = statePtr->connectError; + statePtr->connectError = 0; return -1; } /* * Check if an async connect is running. If not return ok */ + if ( !(statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING) ) return 0; @@ -632,7 +636,7 @@ WaitForConnect( SetEvent(tsdPtr->socketListLock); /* continue connect */ - result = CreateClientSocket(NULL, statePtr); + result = TcpConnect(NULL, statePtr); /* Restore event service mode */ (void) Tcl_SetServiceMode(oldMode); @@ -651,7 +655,7 @@ WaitForConnect( if (errorCodePtr != NULL) { *errorCodePtr = Tcl_GetErrno(); } else { - statePtr->error = Tcl_GetErrno(); + statePtr->connectError = Tcl_GetErrno(); } return -1; } @@ -1256,9 +1260,10 @@ TcpGetOptionProc( * Do not report the system error, as this comes again and again. */ - if ( statePtr->error != 0 ) { - Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(statePtr->error), -1); - statePtr->error = 0; + if ( statePtr->connectError != 0 ) { + Tcl_DStringAppend(dsPtr, + Tcl_ErrnoMsg(statePtr->connectError), -1); + statePtr->connectError = 0; } } else { @@ -1558,7 +1563,7 @@ TcpGetHandleProc( /* *---------------------------------------------------------------------- * - * CreateClientSocket -- + * TcpConnect -- * * This function opens a new socket in client mode. * @@ -1592,7 +1597,7 @@ TcpGetHandleProc( */ static int -CreateClientSocket( +TcpConnect( Tcl_Interp *interp, /* For error reporting; can be NULL. */ TcpState *statePtr) { @@ -1904,7 +1909,7 @@ Tcl_OpenTcpClient( /* * Create a new client socket and wrap it in a channel. */ - if (CreateClientSocket(interp, statePtr) != TCL_OK) { + if (TcpConnect(interp, statePtr) != TCL_OK) { TcpCloseProc(statePtr, NULL); return NULL; } -- cgit v0.12 From f83f7d140e5150b79aa714c448879448d51f5bf5 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 7 Apr 2014 15:11:32 +0000 Subject: Rename CreateClientSocket to TcpConnect --- unix/tclUnixSock.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index d4b7b62..f428811 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -110,7 +110,7 @@ struct TcpState { * Static routines for this file: */ -static int CreateClientSocket(Tcl_Interp *interp, +static int TcpConnect(Tcl_Interp *interp, TcpState *state); static void TcpAccept(ClientData data, int mask); static int TcpBlockModeProc(ClientData data, int mode); @@ -409,13 +409,13 @@ WaitForConnect( if (noblock || (statePtr->flags & TCP_ASYNC_SOCKET)) { if (TclUnixWaitForFile(statePtr->fds.fd, TCL_WRITABLE | TCL_EXCEPTION, 0) != 0) { - CreateClientSocket(NULL, statePtr); + TcpConnect(NULL, statePtr); } } else { while (statePtr->flags & TCP_ASYNC_CONNECT) { if (TclUnixWaitForFile(statePtr->fds.fd, TCL_WRITABLE | TCL_EXCEPTION, -1) != 0) { - CreateClientSocket(NULL, statePtr); + TcpConnect(NULL, statePtr); } } } @@ -936,7 +936,7 @@ TcpGetHandleProc( * * TcpAsyncCallback -- * - * Called by the event handler that CreateClientSocket sets up + * Called by the event handler that TcpConnect sets up * internally for [socket -async] to get notified when the * asyncronous connection attempt has succeeded or failed. * @@ -949,13 +949,13 @@ TcpAsyncCallback( * TCL_READABLE, TCL_WRITABLE and * TCL_EXCEPTION. */ { - CreateClientSocket(NULL, clientData); + TcpConnect(NULL, clientData); } /* *---------------------------------------------------------------------- * - * CreateClientSocket -- + * TcpConnect -- * * This function opens a new socket in client mode. * @@ -983,7 +983,7 @@ TcpAsyncCallback( */ static int -CreateClientSocket( +TcpConnect( Tcl_Interp *interp, /* For error reporting; can be NULL. */ TcpState *statePtr) { @@ -1198,7 +1198,7 @@ Tcl_OpenTcpClient( /* * Create a new client socket and wrap it in a channel. */ - if (CreateClientSocket(interp, statePtr) != TCL_OK) { + if (TcpConnect(interp, statePtr) != TCL_OK) { TcpCloseProc(statePtr, NULL); return NULL; } -- cgit v0.12 From 7d53615f6beb67b4bdc2b0ad35b62c4667a1ab99 Mon Sep 17 00:00:00 2001 From: max Date: Mon, 7 Apr 2014 15:18:09 +0000 Subject: Rename error to connectError in struct TcpState. --- unix/tclUnixSock.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index f428811..adc6243 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -73,7 +73,7 @@ struct TcpState { struct addrinfo *myaddr; /* Iterator over myaddrlist. */ int filehandlers; /* Caches FileHandlers that get set up while * an async socket is not yet connected. */ - int error; /* Cache SO_ERROR of async socket. */ + int connectError; /* Cache SO_ERROR of async socket. */ int cachedBlocking; /* Cache blocking mode of async socket. */ }; @@ -420,7 +420,7 @@ WaitForConnect( } } } - if (statePtr->error != 0) { + if (statePtr->connectError != 0) { return -1; } else { return 0; @@ -463,8 +463,8 @@ TcpInputProc( *errorCodePtr = 0; if (WaitForConnect(statePtr, 0) != 0) { - *errorCodePtr = statePtr->error; - statePtr->error = 0; + *errorCodePtr = statePtr->connectError; + statePtr->connectError = 0; return -1; } bytesRead = recv(statePtr->fds.fd, buf, (size_t) bufSize, 0); @@ -515,8 +515,8 @@ TcpOutputProc( *errorCodePtr = 0; if (WaitForConnect(statePtr, 0) != 0) { - *errorCodePtr = statePtr->error; - statePtr->error = 0; + *errorCodePtr = statePtr->connectError; + statePtr->connectError = 0; return -1; } written = send(statePtr->fds.fd, buf, (size_t) toWrite, 0); @@ -757,9 +757,9 @@ TcpGetOptionProc( if (statePtr->flags & TCP_ASYNC_CONNECT) { /* Suppress errors as long as we are not done */ errno = 0; - } else if (statePtr->error != 0) { - errno = statePtr->error; - statePtr->error = 0; + } else if (statePtr->connectError != 0) { + errno = statePtr->connectError; + statePtr->connectError = 0; } else { int err; getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR, @@ -1070,7 +1070,7 @@ TcpConnect( if (ret < 0 && errno == EINPROGRESS) { Tcl_CreateFileHandler(statePtr->fds.fd, TCL_WRITABLE|TCL_EXCEPTION, TcpAsyncCallback, statePtr); - statePtr->error = errno = EWOULDBLOCK; + statePtr->connectError = errno = EWOULDBLOCK; return TCL_OK; reenter: @@ -1096,7 +1096,7 @@ TcpConnect( } out: - statePtr->error = error; + statePtr->connectError = error; CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); if (async_callback) { /* -- cgit v0.12 From 207329f22a7bf020db137dc4e6d9b9b82d7a4f67 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 8 Apr 2014 14:40:20 +0000 Subject: Changed error report logic, that an async connect error is only reported by 'fconfigure -error' and not by a possible last command terminating the async connect. The terminating command always returns "socket is not connected" on connect error. In addition, some flags were renamed: TCP_ASYNC_SOCKET to TCP_NONBLOCKING and also the new state flags. --- tests/socket.test | 12 ++--- win/tclWinPort.h | 6 +++ win/tclWinSock.c | 146 +++++++++++++++++++++++++++++++++++------------------- 3 files changed, 106 insertions(+), 58 deletions(-) diff --git a/tests/socket.test b/tests/socket.test index d36d2b3..648ade5 100644 --- a/tests/socket.test +++ b/tests/socket.test @@ -1981,10 +1981,10 @@ test socket-14.7.2 {pending [socket -async] and blocking [gets], no listener} \ -body { set sock [socket -async localhost [randport]] catch {gets $sock} x - list $x [fconfigure $sock -error] + list $x [fconfigure $sock -error] [fconfigure $sock -error] } -cleanup { close $sock - } -match glob -result {{error reading "sock*": connection refused} {}} + } -match glob -result {{error reading "sock*": socket is not connected} {connection refused} {}} test socket-14.8.0 {pending [socket -async] and nonblocking [gets], server is IPv4} \ -constraints {socket supported_inet supported_inet6} \ -setup { @@ -2046,10 +2046,10 @@ test socket-14.8.2 {pending [socket -async] and nonblocking [gets], no listener} if {[catch {gets $sock} x] || $x ne "" || ![fblocked $sock]} break after 200 } - list $x [fconfigure $sock -error] + list $x [fconfigure $sock -error] [fconfigure $sock -error] } -cleanup { close $sock - } -match glob -result {{error reading "sock*": connection refused} {}} + } -match glob -result {{error reading "sock*": socket is not connected} {connection refused} {}} test socket-14.9.0 {pending [socket -async] and blocking [puts], server is IPv4} \ -constraints {socket supported_inet supported_inet6} \ -setup { @@ -2164,7 +2164,7 @@ test socket-14.11.0 {pending [socket -async] and blocking [puts], no listener, n } -cleanup { catch {close $sock} unset x - } -result {connection refused} -returnCodes 1 + } -result {socket is not connected} -returnCodes 1 test socket-14.11.1 {pending [socket -async] and blocking [puts], no listener, flush} \ -constraints {socket supported_inet supported_inet6} \ -body { @@ -2178,7 +2178,7 @@ test socket-14.11.1 {pending [socket -async] and blocking [puts], no listener, f } -cleanup { catch {close $sock} unset x - } -result {connection refused} -returnCodes 1 + } -result {socket is not connected} -returnCodes 1 test socket-14.12 {[socket -async] background progress triggered by [fconfigure -error]} \ -constraints {socket supported_inet supported_inet6} \ -body { diff --git a/win/tclWinPort.h b/win/tclWinPort.h index 61f149b..1104b53 100644 --- a/win/tclWinPort.h +++ b/win/tclWinPort.h @@ -532,6 +532,12 @@ typedef DWORD_PTR * PDWORD_PTR; * The following defines map from standard socket names to our internal * wrappers that redirect through the winSock function table (see the * file tclWinSock.c). + * + * Warning: + * This check was useful in times of Windows98 where WinSock may + * not be available. This is not the case any more. + * This function may be removed with TCL 9.0 + * */ #define getservbyname TclWinGetServByName diff --git a/win/tclWinSock.c b/win/tclWinSock.c index 7593396..dc67c4b 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -174,7 +174,9 @@ struct TcpState { struct addrinfo *myaddr; /* Iterator over myaddrlist. */ int connectError; /* Cache status of async socket. */ int cachedBlocking; /* Cache blocking mode of async socket. */ - volatile int connectError; /* Async connect error set by notifier thread. + volatile int notifierConnectError; + /* Async connect error set by notifier thread. + * This error is still a windows error code. * Access must be protected by semaphore */ struct TcpState *nextPtr; /* The next socket on the per-thread socket * list. */ @@ -185,19 +187,17 @@ struct TcpState { * structure. */ -#define TCP_ASYNC_SOCKET (1<<0) /* Asynchronous socket. */ +#define TCP_NONBLOCKING (1<<0) /* Socket with non-blocking I/O */ #define TCP_ASYNC_CONNECT (1<<1) /* Async connect in progress. */ #define SOCKET_EOF (1<<2) /* A zero read happened on the * socket. */ #define SOCKET_PENDING (1<<3) /* A message has been sent for this * socket */ -#define TCP_ASYNC_CONNECT_REENTER_PENDING (1<<4) - /* TcpConnect was called to +#define TCP_ASYNC_PENDING (1<<4) /* TcpConnect was called to * process an async connect. This * flag indicates that reentry is * still pending */ -#define TCP_ASYNC_CONNECT_FAILED (1<<5) - /* An async connect finally failed */ +#define TCP_ASYNC_FAILED (1<<5) /* An async connect finally failed */ /* * The following structure is what is added to the Tcl event queue when a @@ -540,9 +540,9 @@ TcpBlockModeProc( TcpState *statePtr = instanceData; if (mode == TCL_MODE_NONBLOCKING) { - statePtr->flags |= TCP_ASYNC_SOCKET; + statePtr->flags |= TCP_NONBLOCKING; } else { - statePtr->flags &= ~(TCP_ASYNC_SOCKET); + statePtr->flags &= ~(TCP_NONBLOCKING); } return 0; } @@ -554,12 +554,19 @@ TcpBlockModeProc( * * Check the state of an async connect process. If a connection * attempt terminated, process it, which may finalize it or may - * start the next attempt. + * start the next attempt. If a connect error occures, it is saved + * in statePtr->connectError to be reported by 'fconfigure -error'. + * * There are two modes of operation, defined by errorCodePtr: * * non-NULL: Called by explicite read/write command. block if - * socket is blocking. Return a possible error and clear it. - * * Null: Called by a backround operation. Never block and - * save eventual error in statePtr->connectError. + * socket is blocking. + * May return two error codes: + * * EWOULDBLOCK: if connect is still in progress + * * ENOTCONN: if connect failed. This would be the error + * message of a rect or sendto syscall so this is + * emulated here. + * * Null: Called by a backround operation. Do not block and + * don't return any error code. * * Results: * 0 if the connection has completed, -1 if still in progress @@ -577,10 +584,6 @@ WaitForConnect( TcpState *statePtr, /* State of the socket. */ int *errorCodePtr) /* Where to store errors? * A passed null-pointer activates background mode. - * In this case, a possible error is stored in - * statePtr->connectError. - * In addition, we do never block and allow the next - * processing cycle to happen. */ { int result; @@ -588,21 +591,21 @@ WaitForConnect( ThreadSpecificData *tsdPtr; /* - * Check if an async connect error is not jet reported. - * If yes, report it now. + * Check if an async connect failed already and error reporting is demanded, + * return the error ENOTCONN */ - if ( errorCodePtr != NULL && statePtr->connectError != 0 ) { - *errorCodePtr = statePtr->connectError; - statePtr->connectError = 0; + if ( errorCodePtr != NULL && + (statePtr->flags & TCP_ASYNC_FAILED) ) { + *errorCodePtr = ENOTCONN; return -1; } /* * Check if an async connect is running. If not return ok */ - - if ( !(statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING) ) + + if ( !(statePtr->flags & TCP_ASYNC_PENDING) ) return 0; /* @@ -611,6 +614,10 @@ WaitForConnect( oldMode = Tcl_SetServiceMode(TCL_SERVICE_NONE); + /* + * Loop in the blocking case until the connect signal is present + */ + while (1) { /* get statePtr lock */ @@ -628,22 +635,32 @@ WaitForConnect( * disable async connect as we continue now synchoneously */ if ( errorCodePtr != NULL && - ! (statePtr->flags & TCP_ASYNC_SOCKET) ) { + ! (statePtr->flags & TCP_NONBLOCKING) ) { CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); } /* Free list lock */ SetEvent(tsdPtr->socketListLock); - /* continue connect */ + /* + * Continue connect. + * If switched to synchroneous connect, the connect is terminated. + */ result = TcpConnect(NULL, statePtr); /* Restore event service mode */ (void) Tcl_SetServiceMode(oldMode); - /* Succesfully connected or async connect restarted */ + /* + * Check for Succesfull connect or async connect restart + */ + if (result == TCL_OK) { - if ( statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING ) { + /* + * Check for async connect restart + * (not possible for foreground blocking operation) + */ + if ( statePtr->flags & TCP_ASYNC_PENDING ) { if (errorCodePtr != NULL) { *errorCodePtr = EWOULDBLOCK; } @@ -651,11 +668,14 @@ WaitForConnect( } return 0; } - /* error case */ + + /* + * Connect finally failed. + * For foreground operation return ENOTCONN. + */ + if (errorCodePtr != NULL) { - *errorCodePtr = Tcl_GetErrno(); - } else { - statePtr->connectError = Tcl_GetErrno(); + *errorCodePtr = ENOTCONN; } return -1; } @@ -664,14 +684,20 @@ WaitForConnect( SetEvent(tsdPtr->socketListLock); /* - * A non blocking socket waiting for an asyncronous connect - * returns directly an error + * Background operation returns with no action as there was no connect + * event */ + if ( errorCodePtr == NULL ) { - /* Backround operation */ return -1; - } else if (statePtr->flags & TCP_ASYNC_SOCKET) { - /* foreground operation but non blocking socket */ + } + + /* + * A non blocking socket waiting for an asyncronous connect + * returns directly the error EWOULDBLOCK + */ + + if (statePtr->flags & TCP_NONBLOCKING) { *errorCodePtr = EWOULDBLOCK; return -1; } @@ -803,7 +829,7 @@ TcpInputProc( * Check for error condition or underflow in non-blocking case. */ - if ((statePtr->flags & TCP_ASYNC_SOCKET) || (error != WSAEWOULDBLOCK)) { + if ((statePtr->flags & TCP_NONBLOCKING) || (error != WSAEWOULDBLOCK)) { TclWinConvertError(error); *errorCodePtr = Tcl_GetErrno(); bytesRead = -1; @@ -908,7 +934,7 @@ TcpOutputProc( error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) { statePtr->readyEvents &= ~(FD_WRITE); - if (statePtr->flags & TCP_ASYNC_SOCKET) { + if (statePtr->flags & TCP_NONBLOCKING) { *errorCodePtr = EWOULDBLOCK; written = -1; break; @@ -1249,10 +1275,10 @@ TcpGetOptionProc( /* * Do not return any errors if async connect is running */ - if ( ! (statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING) ) { + if ( ! (statePtr->flags & TCP_ASYNC_PENDING) ) { - if ( statePtr->flags & TCP_ASYNC_CONNECT_FAILED ) { + if ( statePtr->flags & TCP_ASYNC_FAILED ) { /* * In case of a failed async connect, eventually report the @@ -1280,7 +1306,7 @@ TcpGetOptionProc( * Populater the err Variable with a possix error */ optlen = sizeof(int); - ret = TclWinGetSockOpt(sock, SOL_SOCKET, SO_ERROR, + ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &optlen); /* * The error was not returned directly but should be @@ -1305,7 +1331,7 @@ TcpGetOptionProc( (strncmp(optionName, "-connecting", len) == 0)) { Tcl_DStringAppend(dsPtr, - (statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING) + (statePtr->flags & TCP_ASYNC_PENDING) ? "1" : "0", -1); return TCL_OK; } @@ -1645,7 +1671,7 @@ TcpConnect( /* * Reset last error from last try */ - statePtr->connectError = 0; + statePtr->notifierConnectError = 0; Tcl_SetErrno(0); statePtr->sockets->fd = socket(statePtr->myaddr->ai_family, SOCK_STREAM, 0); @@ -1747,7 +1773,7 @@ TcpConnect( /* * Remember that we jump back behind this next round */ - statePtr->flags |= TCP_ASYNC_CONNECT_REENTER_PENDING; + statePtr->flags |= TCP_ASYNC_PENDING; return TCL_OK; reenter: @@ -1757,11 +1783,11 @@ TcpConnect( * * Clear the reenter flag */ - statePtr->flags &= ~(TCP_ASYNC_CONNECT_REENTER_PENDING); + statePtr->flags &= ~(TCP_ASYNC_PENDING); /* get statePtr lock */ WaitForSingleObject(tsdPtr->socketListLock, INFINITE); /* Get signaled connect error */ - Tcl_SetErrno(statePtr->connectError); + TclWinConvertError((DWORD) statePtr->notifierConnectError); /* Clear eventual connect flag */ statePtr->selectEvents &= ~(FD_CONNECT); /* Free list lock */ @@ -1819,7 +1845,9 @@ out: /* Signal ready readable and writable events */ statePtr->readyEvents |= FD_WRITE | FD_READ; /* Flag error to event routine */ - statePtr->flags |= TCP_ASYNC_CONNECT_FAILED; + statePtr->flags |= TCP_ASYNC_FAILED; + /* Save connect error to be reported by 'fconfigure -error' */ + statePtr->connectError = Tcl_GetErrno(); /* Free list lock */ SetEvent(tsdPtr->socketListLock); } @@ -2260,6 +2288,12 @@ TcpAccept( * * Assumes socketMutex is held. * + * Warning: + * This check was useful in times of Windows98 where WinSock may + * not be available. This is not the case any more. + * This function may be removed with TCL 9.0. + * Any failures may be reported as panics. + * * Results: * None. * @@ -2393,6 +2427,11 @@ InitSockets(void) * * Check that the WinSock was successfully initialized. * + * Warning: + * This check was useful in times of Windows98 where WinSock may + * not be available. This is not the case any more. + * This function may be removed with TCL 9.0 + * * Results: * 1 if it is. * @@ -2622,7 +2661,7 @@ SocketEventProc( */ if ( statePtr->readyEvents & FD_CONNECT ) { - if ( statePtr->flags & TCP_ASYNC_CONNECT_REENTER_PENDING ) { + if ( statePtr->flags & TCP_ASYNC_PENDING ) { /* * Do one step and save eventual connect error @@ -2735,7 +2774,7 @@ SocketEventProc( * Throw the readable event if an async connect failed. */ - if ( statePtr->flags & TCP_ASYNC_CONNECT_FAILED ) { + if ( statePtr->flags & TCP_ASYNC_FAILED ) { mask |= TCL_READABLE; @@ -2928,7 +2967,7 @@ WaitForSocketEvent( } /* Exit loop if event did not occur but this is a non-blocking channel */ - if (statePtr->flags & TCP_ASYNC_SOCKET) { + if (statePtr->flags & TCP_NONBLOCKING) { *errorCodePtr = EWOULDBLOCK; result = 0; break; @@ -3122,8 +3161,7 @@ SocketProc( * connection failures. */ if (error != ERROR_SUCCESS) { - TclWinConvertError((DWORD) error); - statePtr->connectError = Tcl_GetErrno(); + statePtr->notifierConnectError = error; } } /* @@ -3203,6 +3241,10 @@ FindFDInList( * dynamically so we can run on systems that don't have the wsock32.dll. * We need wrappers for these interfaces because they are called from the * generic Tcl code. + * Those functions are exported by the stubs table. + * + * Warning: + * Those functions are depreciated and will be removed with TCL 9.0. * * Results: * As defined for each function. -- cgit v0.12 From bf345d6be59f6f513be07b6465487f137b9ac820 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 8 Apr 2014 15:15:31 +0000 Subject: Beautify check for async connect reentry --- win/tclWinSock.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/win/tclWinSock.c b/win/tclWinSock.c index dc67c4b..de4c519 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -595,8 +595,7 @@ WaitForConnect( * return the error ENOTCONN */ - if ( errorCodePtr != NULL && - (statePtr->flags & TCP_ASYNC_FAILED) ) { + if (errorCodePtr != NULL && (statePtr->flags & TCP_ASYNC_FAILED)) { *errorCodePtr = ENOTCONN; return -1; } @@ -605,8 +604,9 @@ WaitForConnect( * Check if an async connect is running. If not return ok */ - if ( !(statePtr->flags & TCP_ASYNC_PENDING) ) + if (!(statePtr->flags & TCP_ASYNC_CONNECT)) { return 0; + } /* * Be sure to disable event servicing so we are truly modal. @@ -1635,7 +1635,7 @@ TcpConnect( */ int async_connect = statePtr->flags & TCP_ASYNC_CONNECT; /* We were called by the event procedure and continue our loop */ - int async_callback = statePtr->sockets->fd != INVALID_SOCKET; + int async_callback = statePtr->flags & TCP_ASYNC_PENDING; ThreadSpecificData *tsdPtr = TclThreadDataKeyGet(&dataKey); if (async_callback) { @@ -1811,6 +1811,12 @@ out: * Socket connected or connection failed */ + /* + * Async connect terminated + */ + + CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); + if ( Tcl_GetErrno() == 0 ) { /* * Succesfully connected -- cgit v0.12 From 9e4cc53c71c3d5416cb1e33bc5b47688ba631853 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Apr 2014 18:00:35 +0000 Subject: * Give clearer names to some of the state flags and sync them with Windows where it makes sense. * Rework WaitForConnect once more to always report ENOTCONN on I/O operations on failed async sockets. * Fix synchronous connections to a server that only listens on IPv6 (or whatever comes later in the list returned by getaddrinfo(), socket-15.*) * Fix spurious writable event on async sockets (socket-14.15). --- tests/socket.test | 34 ++++++++++++++ unix/tclUnixSock.c | 131 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 124 insertions(+), 41 deletions(-) diff --git a/tests/socket.test b/tests/socket.test index 648ade5..5ff2109 100644 --- a/tests/socket.test +++ b/tests/socket.test @@ -2225,6 +2225,40 @@ test socket-14.14 {testing fileevent readable on failed async socket connect} -c after cancel $a1 } -result readable +test socket-14.15 {blocking read on async socket should not trigger event handlers} \ + -constraints socket -body { + set s [socket -async localhost [randport]] + set x ok + fileevent $s writable {set x fail} + catch {read $s} + set x + } -result ok + +set num 0 +foreach servip {127.0.0.1 ::1 localhost} { + foreach cliip {127.0.0.1 ::1 localhost} { + if {$servip eq $cliip || "localhost" in [list $servip $cliip]} { + set result {-result "sock*" -match glob} + } else { + set result { + -result {couldn't open socket: connection refused} + -returnCodes 1 + } + } + test socket-15.1.$num "Connect to $servip from $cliip" \ + -constraints {socket supported_inet supported_inet6} -setup { + set server [socket -server accept -myaddr $servip 0] + proc accept {s h p} { close $s } + set port [lindex [fconfigure $server -sockname] 2] + } -body { + set s [socket $cliip $port] + } -cleanup { + close $server + catch {close $s} + } {*}$result + incr num + } +} ::tcltest::cleanupTests flush stdout diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index adc6243..08a14d3 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -82,8 +82,13 @@ struct TcpState { * structure. */ -#define TCP_ASYNC_SOCKET (1<<0) /* Asynchronous socket. */ +#define TCP_NONBLOCKING (1<<0) /* Socket with non-blocking I/O */ #define TCP_ASYNC_CONNECT (1<<1) /* Async connect in progress. */ +#define TCP_ASYNC_PENDING (1<<4) /* TcpConnect was called to + * process an async connect. This + * flag indicates that reentry is + * still pending */ +#define TCP_ASYNC_FAILED (1<<5) /* An async connect finally failed */ /* * The following defines the maximum length of the listen queue. This is the @@ -128,7 +133,7 @@ static int TcpInputProc(ClientData instanceData, char *buf, static int TcpOutputProc(ClientData instanceData, const char *buf, int toWrite, int *errorCode); static void TcpWatchProc(ClientData instanceData, int mask); -static int WaitForConnect(TcpState *statePtr, int noblock); +static int WaitForConnect(TcpState *statePtr, int *errorCodePtr); /* * This structure describes the channel type structure for TCP socket @@ -364,9 +369,9 @@ TcpBlockModeProc( TcpState *statePtr = instanceData; if (mode == TCL_MODE_BLOCKING) { - CLEAR_BITS(statePtr->flags, TCP_ASYNC_SOCKET); + CLEAR_BITS(statePtr->flags, TCP_NONBLOCKING); } else { - SET_BITS(statePtr->flags, TCP_ASYNC_SOCKET); + SET_BITS(statePtr->flags, TCP_NONBLOCKING); } if (statePtr->flags & TCP_ASYNC_CONNECT) { statePtr->cachedBlocking = mode; @@ -383,48 +388,82 @@ TcpBlockModeProc( * * WaitForConnect -- * - * Wait for a connection on an asynchronously opened socket to be - * completed. In nonblocking mode, just test if the connection - * has completed without blocking. The noblock parameter allows to - * enforce nonblocking behaviour even on sockets in blocking mode. + * Check the state of an async connect process. If a connection + * attempt terminated, process it, which may finalize it or may + * start the next attempt. If a connect error occures, it is saved + * in statePtr->connectError to be reported by 'fconfigure -error'. + * + * There are two modes of operation, defined by errorCodePtr: + * * non-NULL: Called by explicite read/write command. block if + * socket is blocking. + * May return two error codes: + * * EWOULDBLOCK: if connect is still in progress + * * ENOTCONN: if connect failed. This would be the error + * message of a rect or sendto syscall so this is + * emulated here. + * * NULL: Called by a backround operation. Do not block and + * don't return any error code. * * Results: * 0 if the connection has completed, -1 if still in progress * or there is an error. * + * Side effects: + * Processes socket events off the system queue. + * May process asynchroneous connect. + * *---------------------------------------------------------------------- */ static int WaitForConnect( TcpState *statePtr, /* State of the socket. */ - int noblock) /* Don't wait, even for sockets in blocking mode */ + int *errorCodePtr) { + int timeout; + /* - * If an asynchronous connect is in progress, attempt to wait for it to - * complete before reading. + * Check if an async connect failed already and error reporting is demanded, + * return the error ENOTCONN */ - if (statePtr->flags & TCP_ASYNC_CONNECT) { - if (noblock || (statePtr->flags & TCP_ASYNC_SOCKET)) { - if (TclUnixWaitForFile(statePtr->fds.fd, - TCL_WRITABLE | TCL_EXCEPTION, 0) != 0) { - TcpConnect(NULL, statePtr); - } - } else { - while (statePtr->flags & TCP_ASYNC_CONNECT) { - if (TclUnixWaitForFile(statePtr->fds.fd, - TCL_WRITABLE | TCL_EXCEPTION, -1) != 0) { - TcpConnect(NULL, statePtr); - } - } - } + if (errorCodePtr != NULL && (statePtr->flags & TCP_ASYNC_FAILED)) { + *errorCodePtr = ENOTCONN; + return -1; + } + + /* + * Check if an async connect is running. If not return ok + */ + + if (!(statePtr->flags & TCP_ASYNC_PENDING)) { + return 0; } - if (statePtr->connectError != 0) { - return -1; + + if (errorCodePtr == NULL || (statePtr->flags & TCP_NONBLOCKING)) { + timeout = 0; } else { - return 0; + timeout = -1; + } + do { + if (TclUnixWaitForFile(statePtr->fds.fd, + TCL_WRITABLE | TCL_EXCEPTION, timeout) != 0) { + TcpConnect(NULL, statePtr); + } + /* Do this only once in the nonblocking case and repeat it until the + * socket is final when blocking */ + } while (timeout == -1 && statePtr->flags & TCP_ASYNC_CONNECT); + + if (errorCodePtr != NULL) { + if (statePtr->flags & TCP_ASYNC_PENDING) { + *errorCodePtr = EAGAIN; + return -1; + } else if (statePtr->connectError != 0) { + *errorCodePtr = ENOTCONN; + return -1; + } } + return 0; } /* @@ -462,9 +501,7 @@ TcpInputProc( int bytesRead; *errorCodePtr = 0; - if (WaitForConnect(statePtr, 0) != 0) { - *errorCodePtr = statePtr->connectError; - statePtr->connectError = 0; + if (WaitForConnect(statePtr, errorCodePtr) != 0) { return -1; } bytesRead = recv(statePtr->fds.fd, buf, (size_t) bufSize, 0); @@ -514,9 +551,7 @@ TcpOutputProc( int written; *errorCodePtr = 0; - if (WaitForConnect(statePtr, 0) != 0) { - *errorCodePtr = statePtr->connectError; - statePtr->connectError = 0; + if (WaitForConnect(statePtr, errorCodePtr) != 0) { return -1; } written = send(statePtr->fds.fd, buf, (size_t) toWrite, 0); @@ -744,7 +779,7 @@ TcpGetOptionProc( TcpState *statePtr = instanceData; size_t len = 0; - WaitForConnect(statePtr, 1); + WaitForConnect(statePtr, NULL); if (optionName != NULL) { len = strlen(optionName); @@ -888,7 +923,7 @@ TcpWatchProc( return; } - if (statePtr->flags & TCP_ASYNC_CONNECT) { + if (statePtr->flags & TCP_ASYNC_PENDING) { /* Async sockets use a FileHandler internally while connecting, so we * need to cache this request until the connection has succeeded. */ statePtr->filehandlers = mask; @@ -988,8 +1023,8 @@ TcpConnect( TcpState *statePtr) { socklen_t optlen; - int async_callback = (statePtr->addr != NULL); - int ret = -1, error = 0; + int async_callback = statePtr->flags & TCP_ASYNC_PENDING; + int ret = -1, error; int async = statePtr->flags & TCP_ASYNC_CONNECT; if (async_callback) { @@ -998,9 +1033,10 @@ TcpConnect( for (statePtr->addr = statePtr->addrlist; statePtr->addr != NULL; statePtr->addr = statePtr->addr->ai_next) { + for (statePtr->myaddr = statePtr->myaddrlist; statePtr->myaddr != NULL; statePtr->myaddr = statePtr->myaddr->ai_next) { - int reuseaddr; + int reuseaddr = 1; /* * No need to try combinations of local and remote addresses of @@ -1047,7 +1083,10 @@ TcpConnect( } } - reuseaddr = 1; + /* Gotta reset the error variable here, before we use it for the + * first time in this iteration. */ + error = 0; + (void) setsockopt(statePtr->fds.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &reuseaddr, sizeof(reuseaddr)); ret = bind(statePtr->fds.fd, statePtr->myaddr->ai_addr, @@ -1070,10 +1109,12 @@ TcpConnect( if (ret < 0 && errno == EINPROGRESS) { Tcl_CreateFileHandler(statePtr->fds.fd, TCL_WRITABLE|TCL_EXCEPTION, TcpAsyncCallback, statePtr); - statePtr->connectError = errno = EWOULDBLOCK; + errno = EWOULDBLOCK; + SET_BITS(statePtr->flags, TCP_ASYNC_PENDING); return TCL_OK; reenter: + CLEAR_BITS(statePtr->flags, TCP_ASYNC_PENDING); Tcl_DeleteFileHandler(statePtr->fds.fd); /* @@ -1106,6 +1147,10 @@ out: TcpWatchProc(statePtr, statePtr->filehandlers); TclUnixSetBlockingMode(statePtr->fds.fd, statePtr->cachedBlocking); + if (error != 0) { + SET_BITS(statePtr->flags, TCP_ASYNC_FAILED); + } + /* * We need to forward the writable event that brought us here, bcasue * upon reading of getsockopt(SO_ERROR), at least some OSes clear the @@ -1115,7 +1160,11 @@ out: * the event mechanism one roundtrip through select(). */ + /* Note: disabling this for now as it causes spurious event triggering + * under Linux (see test socket-14.15). */ +#if 0 Tcl_NotifyChannel(statePtr->channel, TCL_WRITABLE); +#endif } if (error != 0) { /* -- cgit v0.12 From 87f75437c09a8e8fbe5fe0eaa68200c773799e28 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 17 Apr 2014 19:58:41 +0000 Subject: Another test exposing another segfault. --- tests/ioCmd.test | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/ioCmd.test b/tests/ioCmd.test index f021ade..184f773 100644 --- a/tests/ioCmd.test +++ b/tests/ioCmd.test @@ -774,6 +774,22 @@ test iocmd-21.20 {Bug 88aef05cda} -setup { close $ch rename foo {} } -match glob -result {1 {*nested eval*}} +test iocmd-21.21 {[close] in [read] segfaults} -setup { + proc foo {method chan args} { + switch -- $method initialize { + return {initialize finalize watch read} + } finalize {} watch {} read { + close $chan + return a + } + } + set ch [chan create read foo] +} -body { + read $ch 1 +} -cleanup { + close $ch + rename foo {} +} -result a # --- --- --- --------- --------- --------- # Helper commands to record the arguments to handler methods. -- cgit v0.12 From a07adf4d6d28771d9aa74ef06526e5fb3035f5c1 Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 21 Apr 2014 18:55:41 +0000 Subject: Added a refcounting mechanism to ChannelBuffers. Other edits to stop segfaults in tests iocmd-21.2[12]. --- generic/tclIO.c | 50 ++++++++++++++++++++++++++++++++++++++++++-------- generic/tclIO.h | 1 + generic/tclIOCmd.c | 3 +++ tests/ioCmd.test | 20 ++++++++++++++++++-- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 9e675c6..a60c97d 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -162,6 +162,8 @@ typedef struct CloseCallback { */ static ChannelBuffer * AllocChannelBuffer(int length); +static void PreserveChannelBuffer(ChannelBuffer *bufPtr); +static void ReleaseChannelBuffer(ChannelBuffer *bufPtr); static void ChannelTimerProc(ClientData clientData); static int CheckChannelErrors(ChannelState *statePtr, int direction); @@ -352,6 +354,7 @@ ChanRead( int *errnoPtr) { if (WillRead(chanPtr) < 0) { + *errnoPtr = Tcl_GetErrno(); return -1; } @@ -2216,8 +2219,26 @@ AllocChannelBuffer( bufPtr->nextRemoved = BUFFER_PADDING; bufPtr->bufLength = length + BUFFER_PADDING; bufPtr->nextPtr = NULL; + bufPtr->refCount = 1; return bufPtr; } + +static void +PreserveChannelBuffer( + ChannelBuffer *bufPtr) +{ + bufPtr->refCount++; +} + +static void +ReleaseChannelBuffer( + ChannelBuffer *bufPtr) +{ + if (--bufPtr->refCount) { + return; + } + ckfree((char *) bufPtr); +} /* *---------------------------------------------------------------------- @@ -2250,7 +2271,7 @@ RecycleBuffer( */ if (mustDiscard) { - ckfree((char *) bufPtr); + ReleaseChannelBuffer(bufPtr); return; } @@ -2261,7 +2282,7 @@ RecycleBuffer( */ if ((bufPtr->bufLength - BUFFER_PADDING) < statePtr->bufSize) { - ckfree((char *) bufPtr); + ReleaseChannelBuffer(bufPtr); return; } @@ -2296,7 +2317,7 @@ RecycleBuffer( * If we reached this code we return the buffer to the OS. */ - ckfree((char *) bufPtr); + ReleaseChannelBuffer(bufPtr); return; keepBuffer: @@ -2470,6 +2491,7 @@ FlushChannel( * Produce the output on the channel. */ + PreserveChannelBuffer(bufPtr); toWrite = BytesLeft(bufPtr); if (toWrite == 0) { written = 0; @@ -2597,6 +2619,7 @@ FlushChannel( } RecycleBuffer(statePtr, bufPtr, 0); } + ReleaseChannelBuffer(bufPtr); } /* Closes "while (1)". */ /* @@ -2681,7 +2704,7 @@ CloseChannel( */ if (statePtr->curOutPtr != NULL) { - ckfree((char *) statePtr->curOutPtr); + ReleaseChannelBuffer(statePtr->curOutPtr); statePtr->curOutPtr = NULL; } @@ -3566,6 +3589,11 @@ static void WillWrite(Channel *chanPtr) static int WillRead(Channel *chanPtr) { + if (chanPtr->typePtr == NULL) { + /* Prevent read attempts on a closed channel */ + Tcl_SetErrno(EINVAL); + return -1; + } if ((chanPtr->typePtr->seekProc != NULL) && (Tcl_OutputBuffered((Tcl_Channel) chanPtr) > 0)) { if ((chanPtr->state->curOutPtr != NULL) @@ -3652,6 +3680,7 @@ Write( bufPtr->nextAdded += saved; saved = 0; } + PreserveChannelBuffer(bufPtr); dst = InsertPoint(bufPtr); dstLen = SpaceLeft(bufPtr); @@ -3665,6 +3694,7 @@ Write( if ((result != TCL_OK) && (srcRead + dstWrote == 0)) { /* We're reading from invalid/incomplete UTF-8 */ + ReleaseChannelBuffer(bufPtr); if (total == 0) { Tcl_SetErrno(EINVAL); return -1; @@ -3748,6 +3778,7 @@ Write( needNlFlush = 0; } } + ReleaseChannelBuffer(bufPtr); } if ((flushed < total) && (statePtr->flags & CHANNEL_UNBUFFERED || (needNlFlush && statePtr->flags & CHANNEL_LINEBUFFERED))) { @@ -5935,7 +5966,7 @@ DiscardInputQueued( */ if (discardSavedBuffers && statePtr->saveInBufPtr != NULL) { - ckfree((char *) statePtr->saveInBufPtr); + ReleaseChannelBuffer(statePtr->saveInBufPtr); statePtr->saveInBufPtr = NULL; } } @@ -6028,7 +6059,7 @@ GetInput( if ((bufPtr != NULL) && (bufPtr->bufLength - BUFFER_PADDING < statePtr->bufSize)) { - ckfree((char *) bufPtr); + ReleaseChannelBuffer(bufPtr); bufPtr = NULL; } @@ -6090,6 +6121,7 @@ GetInput( } else { #endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ + PreserveChannelBuffer(bufPtr); nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead, &result); #ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING @@ -6097,6 +6129,7 @@ GetInput( #endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ if (nread > 0) { + result = 0; bufPtr->nextAdded += nread; /* @@ -6122,6 +6155,7 @@ GetInput( #endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ } else if (nread == 0) { + result = 0; SetFlag(statePtr, CHANNEL_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; } else if (nread < 0) { @@ -6130,9 +6164,9 @@ GetInput( result = EAGAIN; } Tcl_SetErrno(result); - return result; } - return 0; + ReleaseChannelBuffer(bufPtr); + return result; } /* diff --git a/generic/tclIO.h b/generic/tclIO.h index ebf2ef7..0ea7d1c 100644 --- a/generic/tclIO.h +++ b/generic/tclIO.h @@ -36,6 +36,7 @@ */ typedef struct ChannelBuffer { + int refCount; /* Current uses count */ int nextAdded; /* The next position into which a character * will be put in the buffer. */ int nextRemoved; /* Position of next byte to be removed from diff --git a/generic/tclIOCmd.c b/generic/tclIOCmd.c index 2958bc8..7e8e91a 100644 --- a/generic/tclIOCmd.c +++ b/generic/tclIOCmd.c @@ -447,6 +447,7 @@ Tcl_ReadObjCmd( resultPtr = Tcl_NewObj(); Tcl_IncrRefCount(resultPtr); + Tcl_Preserve(chan); charactersRead = Tcl_ReadChars(chan, resultPtr, toRead, 0); if (charactersRead < 0) { /* @@ -462,6 +463,7 @@ Tcl_ReadObjCmd( TclGetString(chanObjPtr), "\": ", Tcl_PosixError(interp), NULL); } + Tcl_Release(chan); Tcl_DecrRefCount(resultPtr); return TCL_ERROR; } @@ -480,6 +482,7 @@ Tcl_ReadObjCmd( } } Tcl_SetObjResult(interp, resultPtr); + Tcl_Release(chan); Tcl_DecrRefCount(resultPtr); return TCL_OK; } diff --git a/tests/ioCmd.test b/tests/ioCmd.test index 184f773..8bf32d2 100644 --- a/tests/ioCmd.test +++ b/tests/ioCmd.test @@ -785,11 +785,27 @@ test iocmd-21.21 {[close] in [read] segfaults} -setup { } set ch [chan create read foo] } -body { - read $ch 1 + read $ch 0 } -cleanup { close $ch rename foo {} -} -result a +} -result {} +test iocmd-21.22 {[close] in [read] segfaults} -setup { + proc foo {method chan args} { + switch -- $method initialize { + return {initialize finalize watch read} + } finalize {} watch {} read { + catch {close $chan} + return a + } + } + set ch [chan create read foo] +} -body { + read $ch 1 +} -returnCodes error -cleanup { + catch {close $ch} + rename foo {} +} -match glob -result {*invalid argument*} # --- --- --- --------- --------- --------- # Helper commands to record the arguments to handler methods. -- cgit v0.12 From 192fce9c01c4ee3827aa8f54967764c18bdd5dca Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 22 Apr 2014 13:25:41 +0000 Subject: Memory leak after thread exit, fixed (alloc cache released by exit), belong to ticket [3493120] Moved over to branch bug-3493120. This is not ready for the core-8-5-branch. Segfaults all over the place in a thread-enabled build on a CentOS system. --- generic/tclInt.h | 1 + generic/tclThread.c | 8 ++++++-- generic/tclThreadAlloc.c | 27 +++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/generic/tclInt.h b/generic/tclInt.h index 1348340..00b246b 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2550,6 +2550,7 @@ MODULE_SCOPE void TclFinalizeObjects(void); MODULE_SCOPE void TclFinalizePreserve(void); MODULE_SCOPE void TclFinalizeSynchronization(void); MODULE_SCOPE void TclFinalizeThreadAlloc(void); +MODULE_SCOPE void TclFinalizeThreadAllocThread(void); MODULE_SCOPE void TclFinalizeThreadData(void); MODULE_SCOPE void TclFinalizeThreadObjects(void); MODULE_SCOPE double TclFloor(mp_int *a); diff --git a/generic/tclThread.c b/generic/tclThread.c index 8384107..d6b5bcb 100644 --- a/generic/tclThread.c +++ b/generic/tclThread.c @@ -338,8 +338,9 @@ Tcl_ConditionFinalize( * * TclFinalizeThreadData -- * - * This function cleans up the thread-local storage. This is called once - * for each thread. + * This function cleans up the thread-local storage. Secondary, it cleans + * thread alloc cache. + * This is called once for each thread before thread exits. * * Results: * None. @@ -354,6 +355,9 @@ void TclFinalizeThreadData(void) { TclpFinalizeThreadDataThread(); +#if defined(TCL_THREADS) && defined(USE_THREAD_ALLOC) + TclFinalizeThreadAllocThread(); +#endif } /* diff --git a/generic/tclThreadAlloc.c b/generic/tclThreadAlloc.c index 2e74fa7..106e908 100644 --- a/generic/tclThreadAlloc.c +++ b/generic/tclThreadAlloc.c @@ -1012,6 +1012,33 @@ TclFinalizeThreadAlloc(void) TclpFreeAllocCache(NULL); } +/* + *---------------------------------------------------------------------- + * + * TclFinalizeThreadAllocThread -- + * + * This procedure is used to destroy single thread private resources used + * in this file. + * Called in TclpFinalizeThreadData when a thread exits (Tcl_FinalizeThread). + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclFinalizeThreadAllocThread(void) +{ + Cache *cachePtr = TclpGetAllocCache(); + if (cachePtr != NULL) { + TclpFreeAllocCache(cachePtr); + } +} + #else /* !(TCL_THREADS && USE_THREAD_ALLOC) */ /* *---------------------------------------------------------------------- -- cgit v0.12 From 190e2f09e8fa5bc975ba496a5544b4dd853378f0 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 22 Apr 2014 14:57:44 +0000 Subject: Add refcounting and preservation to [testchannel transform] to stop segfault in test iogt-2.4. --- generic/tclIOGT.c | 122 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/generic/tclIOGT.c b/generic/tclIOGT.c index eed21fb..beddf4f 100644 --- a/generic/tclIOGT.c +++ b/generic/tclIOGT.c @@ -210,7 +210,27 @@ struct TransformChannelData { * a transformation of incoming data. Also * serves as buffer of all data not yet * consumed by the reader. */ + int refCount; }; + +static void +PreserveData( + TransformChannelData *dataPtr) +{ + dataPtr->refCount++; +} + +static void +ReleaseData( + TransformChannelData *dataPtr) +{ + if (--dataPtr->refCount) { + return; + } + ResultClear(&dataPtr->result); + Tcl_DecrRefCount(dataPtr->command); + ckfree((char *) dataPtr); +} /* *---------------------------------------------------------------------- @@ -240,6 +260,7 @@ TclChannelTransform( Channel *chanPtr; /* The actual channel. */ ChannelState *statePtr; /* State info for channel. */ int mode; /* Read/write mode of the channel. */ + int objc; TransformChannelData *dataPtr; Tcl_DString ds; @@ -247,6 +268,12 @@ TclChannelTransform( return TCL_ERROR; } + if (TCL_OK != Tcl_ListObjLength(interp, cmdObjPtr, &objc)) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("-command value is not a list", -1)); + return TCL_ERROR; + } + chanPtr = (Channel *) chan; statePtr = chanPtr->state; chanPtr = statePtr->topChanPtr; @@ -261,6 +288,7 @@ TclChannelTransform( dataPtr = (TransformChannelData *) ckalloc(sizeof(TransformChannelData)); + dataPtr->refCount = 1; Tcl_DStringInit(&ds); Tcl_GetChannelOption(interp, chan, "-blocking", &ds); dataPtr->readIsFlushed = 0; @@ -286,9 +314,7 @@ TclChannelTransform( if (dataPtr->self == NULL) { Tcl_AppendResult(interp, "\nfailed to stack channel \"", Tcl_GetChannelName(chan), "\"", NULL); - Tcl_DecrRefCount(dataPtr->command); - ResultClear(&dataPtr->result); - ckfree((char *) dataPtr); + ReleaseData(dataPtr); return TCL_ERROR; } @@ -296,9 +322,11 @@ TclChannelTransform( * At last initialize the transformation at the script level. */ + PreserveData(dataPtr); if ((dataPtr->mode & TCL_WRITABLE) && ExecuteCallback(dataPtr, NULL, A_CREATE_WRITE, NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE) != TCL_OK){ Tcl_UnstackChannel(interp, chan); + ReleaseData(dataPtr); return TCL_ERROR; } @@ -307,9 +335,11 @@ TclChannelTransform( ExecuteCallback(dataPtr, NULL, A_DELETE_WRITE, NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE); Tcl_UnstackChannel(interp, chan); + ReleaseData(dataPtr); return TCL_ERROR; } + ReleaseData(dataPtr); return TCL_OK; } @@ -350,7 +380,10 @@ ExecuteCallback( unsigned char *resBuf; Tcl_InterpState state = NULL; int res = TCL_OK; - Tcl_Obj *command = Tcl_DuplicateObj(dataPtr->command); + Tcl_Obj *command = TclListObjCopy(NULL, dataPtr->command); + Tcl_Interp *eval = dataPtr->interp; + + Tcl_Preserve(eval); /* * Step 1, create the complete command to execute. Do this by appending @@ -361,26 +394,18 @@ ExecuteCallback( */ if (preserve == P_PRESERVE) { - state = Tcl_SaveInterpState(dataPtr->interp, res); + state = Tcl_SaveInterpState(eval, res); } Tcl_IncrRefCount(command); - res = Tcl_ListObjAppendElement(dataPtr->interp, command, - Tcl_NewStringObj((char *) op, -1)); - if (res != TCL_OK) { - goto cleanup; - } + Tcl_ListObjAppendElement(NULL, command, Tcl_NewStringObj((char *) op, -1)); /* * Use a byte-array to prevent the misinterpretation of binary data coming * through as UTF while at the tcl level. */ - res = Tcl_ListObjAppendElement(dataPtr->interp, command, - Tcl_NewByteArrayObj(buf, bufLen)); - if (res != TCL_OK) { - goto cleanup; - } + Tcl_ListObjAppendElement(NULL, command, Tcl_NewByteArrayObj(buf, bufLen)); /* * Step 2, execute the command at the global level of the interpreter used @@ -390,13 +415,14 @@ ExecuteCallback( * current interpreter. Don't copy if in preservation mode. */ - res = Tcl_EvalObjEx(dataPtr->interp, command, TCL_EVAL_GLOBAL); + res = Tcl_EvalObjEx(eval, command, TCL_EVAL_GLOBAL); Tcl_DecrRefCount(command); command = NULL; - if ((res != TCL_OK) && (interp != NULL) && (dataPtr->interp != interp) + if ((res != TCL_OK) && (interp != NULL) && (eval != interp) && (preserve == P_NO_PRESERVE)) { - Tcl_SetObjResult(interp, Tcl_GetObjResult(dataPtr->interp)); + Tcl_SetObjResult(interp, Tcl_GetObjResult(eval)); + Tcl_Release(eval); return res; } @@ -411,20 +437,20 @@ ExecuteCallback( break; case TRANSMIT_DOWN: - resObj = Tcl_GetObjResult(dataPtr->interp); + resObj = Tcl_GetObjResult(eval); resBuf = Tcl_GetByteArrayFromObj(resObj, &resLen); Tcl_WriteRaw(Tcl_GetStackedChannel(dataPtr->self), (char *) resBuf, resLen); break; case TRANSMIT_SELF: - resObj = Tcl_GetObjResult(dataPtr->interp); + resObj = Tcl_GetObjResult(eval); resBuf = Tcl_GetByteArrayFromObj(resObj, &resLen); Tcl_WriteRaw(dataPtr->self, (char *) resBuf, resLen); break; case TRANSMIT_IBUF: - resObj = Tcl_GetObjResult(dataPtr->interp); + resObj = Tcl_GetObjResult(eval); resBuf = Tcl_GetByteArrayFromObj(resObj, &resLen); ResultAdd(&dataPtr->result, resBuf, resLen); break; @@ -434,24 +460,16 @@ ExecuteCallback( * Interpret result as integer number. */ - resObj = Tcl_GetObjResult(dataPtr->interp); - TclGetIntFromObj(dataPtr->interp, resObj, &dataPtr->maxRead); + resObj = Tcl_GetObjResult(eval); + TclGetIntFromObj(eval, resObj, &dataPtr->maxRead); break; } - Tcl_ResetResult(dataPtr->interp); - if (preserve == P_PRESERVE) { - (void) Tcl_RestoreInterpState(dataPtr->interp, state); - } - return res; - - cleanup: + Tcl_ResetResult(eval); if (preserve == P_PRESERVE) { - (void) Tcl_RestoreInterpState(dataPtr->interp, state); - } - if (command != NULL) { - Tcl_DecrRefCount(command); + (void) Tcl_RestoreInterpState(eval, state); } + Tcl_Release(eval); return res; } @@ -535,6 +553,7 @@ TransformCloseProc( * system rely on (f.e. signaling the close to interested parties). */ + PreserveData(dataPtr); if (dataPtr->mode & TCL_WRITABLE) { ExecuteCallback(dataPtr, interp, A_FLUSH_WRITE, NULL, 0, TRANSMIT_DOWN, P_PRESERVE); @@ -554,14 +573,13 @@ TransformCloseProc( ExecuteCallback(dataPtr, interp, A_DELETE_READ, NULL, 0, TRANSMIT_DONT, P_PRESERVE); } + ReleaseData(dataPtr); /* * General cleanup. */ - ResultClear(&dataPtr->result); - Tcl_DecrRefCount(dataPtr->command); - ckfree((char *) dataPtr); + ReleaseData(dataPtr); return TCL_OK; } @@ -606,6 +624,7 @@ TransformInputProc( gotBytes = 0; downChan = Tcl_GetStackedChannel(dataPtr->self); + PreserveData(dataPtr); while (toRead > 0) { /* * Loop until the request is satisfied (or no data is available from @@ -623,7 +642,7 @@ TransformInputProc( * break out of the loop and return to the caller. */ - return gotBytes; + break; } /* @@ -647,7 +666,7 @@ TransformInputProc( } } /* else: 'maxRead < 0' == Accept the current value of toRead. */ if (toRead <= 0) { - return gotBytes; + break; } /* @@ -663,11 +682,12 @@ TransformInputProc( */ if ((Tcl_GetErrno() == EAGAIN) && (gotBytes > 0)) { - return gotBytes; + break; } *errorCodePtr = Tcl_GetErrno(); - return -1; + gotBytes = -1; + break; } else if (read == 0) { /* * Check wether we hit on EOF in the underlying channel or not. If @@ -682,9 +702,9 @@ TransformInputProc( if (!Tcl_Eof(downChan)) { if ((gotBytes == 0) && (dataPtr->flags & CHANNEL_ASYNC)) { *errorCodePtr = EWOULDBLOCK; - return -1; + gotBytes = -1; } - return gotBytes; + break; } if (dataPtr->readIsFlushed) { @@ -692,7 +712,7 @@ TransformInputProc( * Already flushed, nothing to do anymore. */ - return gotBytes; + break; } dataPtr->readIsFlushed = 1; @@ -704,7 +724,7 @@ TransformInputProc( * We had nothing to flush. */ - return gotBytes; + break; } continue; /* at: while (toRead > 0) */ @@ -718,9 +738,11 @@ TransformInputProc( if (ExecuteCallback(dataPtr, NULL, A_READ, UCHARP(buf), read, TRANSMIT_IBUF, P_PRESERVE) != TCL_OK) { *errorCodePtr = EINVAL; - return -1; + gotBytes = -1; + break; } } /* while toRead > 0 */ + ReleaseData(dataPtr); return gotBytes; } @@ -762,11 +784,13 @@ TransformOutputProc( return 0; } + PreserveData(dataPtr); if (ExecuteCallback(dataPtr, NULL, A_WRITE, UCHARP(buf), toWrite, TRANSMIT_DOWN, P_NO_PRESERVE) != TCL_OK) { *errorCodePtr = EINVAL; - return -1; + toWrite = -1; } + ReleaseData(dataPtr); return toWrite; } @@ -819,6 +843,7 @@ TransformSeekProc( * request down, unchanged. */ + PreserveData(dataPtr); if (dataPtr->mode & TCL_WRITABLE) { ExecuteCallback(dataPtr, NULL, A_FLUSH_WRITE, NULL, 0, TRANSMIT_DOWN, P_NO_PRESERVE); @@ -830,6 +855,7 @@ TransformSeekProc( ResultClear(&dataPtr->result); dataPtr->readIsFlushed = 0; } + ReleaseData(dataPtr); return parentSeekProc(Tcl_GetChannelInstanceData(parent), offset, mode, errorCodePtr); @@ -890,6 +916,7 @@ TransformWideSeekProc( * request down, unchanged. */ + PreserveData(dataPtr); if (dataPtr->mode & TCL_WRITABLE) { ExecuteCallback(dataPtr, NULL, A_FLUSH_WRITE, NULL, 0, TRANSMIT_DOWN, P_NO_PRESERVE); @@ -901,6 +928,7 @@ TransformWideSeekProc( ResultClear(&dataPtr->result); dataPtr->readIsFlushed = 0; } + ReleaseData(dataPtr); /* * If we have a wide seek capability, we should stick with that. -- cgit v0.12 From f231f10b58b9f58583b664655b513d3f3ee0c382 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 23 Apr 2014 09:18:20 +0000 Subject: *nix segfault cleared: we should reset a thread key after freeing of alloc cache (in tclUnixThrd.c) --- unix/tclUnixThrd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/unix/tclUnixThrd.c b/unix/tclUnixThrd.c index ad36242..d30791d 100644 --- a/unix/tclUnixThrd.c +++ b/unix/tclUnixThrd.c @@ -814,6 +814,7 @@ TclpFreeAllocCache( */ TclFreeAllocCache(ptr); + pthread_setspecific(key, NULL); } else if (initialized) { /* -- cgit v0.12 From 790e5adaf4cabf6c9dcaa3d109427dbe18f786ff Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 24 Apr 2014 15:27:58 +0000 Subject: Make sure the ReflectedChannel struct is freed in the handler thread, where it was allocated. This constraint allows the struct to safely hold Tcl_Obj values, which has been convenient for storing callback commands. --- generic/tclIORChan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generic/tclIORChan.c b/generic/tclIORChan.c index e462f61..94428bb 100644 --- a/generic/tclIORChan.c +++ b/generic/tclIORChan.c @@ -1145,6 +1145,7 @@ ReflectClose( if (result != TCL_OK) { FreeReceivedError(&p); } + return EOK; } #endif @@ -1169,8 +1170,6 @@ ReflectClose( Tcl_DeleteEvents(ReflectEventDelete, rcPtr); - Tcl_EventuallyFree(rcPtr, (Tcl_FreeProc *) FreeReflectedChannel); - if (result != TCL_OK) { PassReceivedErrorInterp(interp, &p); } @@ -2903,6 +2902,7 @@ ForwardProc( Tcl_GetChannelName(rcPtr->chan)); Tcl_DeleteHashEntry(hPtr); + Tcl_EventuallyFree(rcPtr, (Tcl_FreeProc *) FreeReflectedChannel); break; case ForwardedInput: { -- cgit v0.12 From 0781259dd17444340c1a926c4cd2b5ade72bfebe Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 25 Apr 2014 17:34:11 +0000 Subject: Test iortrans-4.8.2 demos an infinite loop. Possible trouble with pushback buffers. --- generic/tclIO.c | 5 +++++ tests/ioTrans.test | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index e6439ef..41ac1e1 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5611,6 +5611,11 @@ DoReadChars( ResetFlag(statePtr, CHANNEL_BLOCKED); } result = GetInput(chanPtr); +if (chanPtr != statePtr->topChanPtr) { +Tcl_Release(chanPtr); +chanPtr = statePtr->topChanPtr; +Tcl_Preserve(chanPtr); +} if (result != 0) { if (result == EAGAIN) { break; diff --git a/tests/ioTrans.test b/tests/ioTrans.test index b21d894..3bbd170 100644 --- a/tests/ioTrans.test +++ b/tests/ioTrans.test @@ -559,6 +559,26 @@ test iortrans-4.8.1 {chan read, bug 721ec69271} -setup { rename foo {} } -result {{read rt* {test data }} file*} +test iortrans-4.8.2 {chan read, bug 721ec69271} -setup { + set res {} +} -match glob -body { + proc foo {fd args} { + handle.initialize + handle.finalize + lappend ::res $args + # Kill and recreate transform while it is operating + chan pop $fd + chan push $fd [list foo $fd] + return x + } + set c [chan push [set c [tempchan]] [list foo $c]] + chan configure $c -buffersize 1 + lappend res [read $c] +} -cleanup { + tempdone + rename foo {} +} -result {{read rt* {test data +}} file*} test iortrans-4.9 {chan read, gets, bug 2921116} -setup { set res {} } -match glob -body { -- cgit v0.12 From 4119a864755c221944bcd1967b8243a2acc3d9aa Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 25 Apr 2014 19:51:36 +0000 Subject: Disable buffer recycling, which creates mysteries. --- generic/tclIO.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 41ac1e1..df863cc 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2342,7 +2342,7 @@ RecycleBuffer( * Do we have to free the buffer to the OS? */ - if (mustDiscard) { + if (1 || mustDiscard) { ReleaseChannelBuffer(bufPtr); return; } -- cgit v0.12 From 1fc337bda0ffe4523e3a47f27077378b4c1349cf Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 25 Apr 2014 20:12:48 +0000 Subject: Disable buffer recycling to expose bugs for fixing. --- generic/tclIO.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index a60c97d..3f9ca0a 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2270,7 +2270,7 @@ RecycleBuffer( * Do we have to free the buffer to the OS? */ - if (mustDiscard) { + if (1 || mustDiscard) { ReleaseChannelBuffer(bufPtr); return; } -- cgit v0.12 From 9a76d245a9dcf48449c6f147252a9be6b43abf09 Mon Sep 17 00:00:00 2001 From: ferrieux Date: Mon, 28 Apr 2014 20:28:10 +0000 Subject: Clarify fcopy manpage regarding its bidirectional uses. [1350564] --- doc/fcopy.n | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/doc/fcopy.n b/doc/fcopy.n index ec3d5c6..071896c 100644 --- a/doc/fcopy.n +++ b/doc/fcopy.n @@ -46,8 +46,11 @@ non-blocking mode; the \fBfcopy\fR command takes care of that automatically. However, it is necessary to enter the event loop by using the \fBvwait\fR command or by using Tk. .PP -You are not allowed to do other I/O operations with -\fIinchan\fR or \fIoutchan\fR during a background \fBfcopy\fR. +You are not allowed to do other input operations with \fIinchan\fR, or +output operations with \fIoutchan\fR, during a background +\fBfcopy\fR. The converse is entirely legitimate, as exhibited by the +bidirectional fcopy example below. +.PP If either \fIinchan\fR or \fIoutchan\fR get closed while the copy is in progress, the current copy is stopped and the command callback is \fInot\fR made. @@ -57,7 +60,7 @@ then all data already queued for \fIoutchan\fR is written out. Note that \fIinchan\fR can become readable during a background copy. You should turn off any \fBfileevent\fR handlers during a background copy so those handlers do not interfere with the copy. -Any I/O attempted by a \fBfileevent\fR handler will get a +Any wrong-sided I/O attempted (by a \fBfileevent\fR handler or otherwise) will get a .QW "channel busy" error. .PP @@ -149,6 +152,24 @@ set total 0 -command [list CopyMore $in $out $chunk] vwait done .CE +.PP +The fourth example starts an asynchronous, bidirectional fcopy between +two sockets. Those could also be pipes from two [open "|hal 9000" r+] +(though their conversation would remain secret to the script, since +all four fileevent slots are busy). +.PP +.CS +set flows 2 +proc Done {dir args} { + global flows done + puts "$dir is over." + incr flows -1 + if {$flows<=0} {set done 1} +} +\fBfcopy\fR $sok1 $sok2 -command [list Done UP] +\fBfcopy\fR $sok2 $sok1 -command [list Done DOWN] +vwait done +.CE .SH "SEE ALSO" eof(n), fblocked(n), fconfigure(n), file(n) .SH KEYWORDS -- cgit v0.12 From 0725c745416a8cfec6c72440cfc62d5fdc686f04 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 29 Apr 2014 15:54:03 +0000 Subject: Revise the logic for setting TCL_ENCODING_END in the outputEncodingFlags so it does not rely on buffer recycling. --- generic/tclIO.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 3f9ca0a..6ff8806 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -3142,7 +3142,8 @@ Tcl_Close( stickyError = 0; - if ((statePtr->encoding != NULL) && (statePtr->curOutPtr != NULL) + if ((statePtr->encoding != NULL) + && !(statePtr->outputEncodingFlags & TCL_ENCODING_START) && (CheckChannelErrors(statePtr, TCL_WRITABLE) == 0)) { statePtr->outputEncodingFlags |= TCL_ENCODING_END; if (WriteChars(chanPtr, "", 0) < 0) { @@ -7330,7 +7331,8 @@ Tcl_SetChannelOption( * iso2022, the terminated escape sequence must write to the buffer. */ - if ((statePtr->encoding != NULL) && (statePtr->curOutPtr != NULL) + if ((statePtr->encoding != NULL) + && !(statePtr->outputEncodingFlags & TCL_ENCODING_START) && (CheckChannelErrors(statePtr, TCL_WRITABLE) == 0)) { statePtr->outputEncodingFlags |= TCL_ENCODING_END; WriteChars(chanPtr, "", 0); -- cgit v0.12 From 267c1eb9f4c15531f7bf4095ffb56151ad8f9203 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 29 Apr 2014 17:40:15 +0000 Subject: Make sure no shared ChannelBuffers get recycled. --- generic/tclIO.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index 08d0d93..6831c47 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -164,6 +164,7 @@ typedef struct CloseCallback { static ChannelBuffer * AllocChannelBuffer(int length); static void PreserveChannelBuffer(ChannelBuffer *bufPtr); static void ReleaseChannelBuffer(ChannelBuffer *bufPtr); +static int IsShared(ChannelBuffer *bufPtr); static void ChannelTimerProc(ClientData clientData); static int CheckChannelErrors(ChannelState *statePtr, int direction); @@ -2239,6 +2240,13 @@ ReleaseChannelBuffer( } ckfree((char *) bufPtr); } + +static int +IsShared( + ChannelBuffer *bufPtr) +{ + return bufPtr->refCount > 1; +} /* *---------------------------------------------------------------------- @@ -2269,6 +2277,9 @@ RecycleBuffer( /* * Do we have to free the buffer to the OS? */ + if (IsShared(bufPtr)) { + mustDiscard = 1; + } if (mustDiscard) { ReleaseChannelBuffer(bufPtr); -- cgit v0.12 From 75c22ad9870b02a3a9b9d213a1d38a1e93a7f7e7 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 30 Apr 2014 19:12:37 +0000 Subject: Another segfault demo test, this one with [close] during [gets]. --- tests/ioCmd.test | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/ioCmd.test b/tests/ioCmd.test index 8bf32d2..d2c0173 100644 --- a/tests/ioCmd.test +++ b/tests/ioCmd.test @@ -806,6 +806,22 @@ test iocmd-21.22 {[close] in [read] segfaults} -setup { catch {close $ch} rename foo {} } -match glob -result {*invalid argument*} +test iocmd-21.23 {[close] in [gets] segfaults} -setup { + proc foo {method chan args} { + switch -- $method initialize { + return {initialize finalize watch read} + } finalize {} watch {} read { + catch {close $chan} + return \n + } + } + set ch [chan create read foo] +} -body { + gets $ch +} -returnCodes error -cleanup { + catch {close $ch} + rename foo {} +} -match glob -result {*invalid argument*} # --- --- --- --------- --------- --------- # Helper commands to record the arguments to handler methods. -- cgit v0.12 From 1264e873bbaae9d4328826b749e459e88b32e820 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 30 Apr 2014 19:33:21 +0000 Subject: Panic message to pinpoint the cause of iocmd-21.23 segfault. --- generic/tclIO.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index 6831c47..0c9db67 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4570,6 +4570,9 @@ FilterInputBytes( } bufPtr = statePtr->inQueueTail; gsPtr->bufPtr = bufPtr; + if (bufPtr == NULL) { + Tcl_Panic("GetInput nuked buffers!"); + } } /* -- cgit v0.12 From 1c909a3352991d577a7164d5bb33dbe8d295ae67 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 30 Apr 2014 19:54:51 +0000 Subject: Stop the segfaults in [close] during [gets] tests. Not sure this is the right behavior, but it's better than crashing. --- generic/tclIO.c | 23 ++++++++++++++--------- tests/ioCmd.test | 21 +++++++++++++++++++-- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 0c9db67..cd28a0e 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4163,12 +4163,12 @@ Tcl_GetsObj( restore: bufPtr = statePtr->inQueueHead; - if (bufPtr == NULL) { - Tcl_Panic("Tcl_GetsObj: restore reached with bufPtr==NULL"); + if (bufPtr != NULL) { + bufPtr->nextRemoved = oldRemoved; + bufPtr = bufPtr->nextPtr; } - bufPtr->nextRemoved = oldRemoved; - for (bufPtr = bufPtr->nextPtr; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { + for ( ; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bufPtr->nextRemoved = BUFFER_PADDING; } CommonGetsCleanup(chanPtr); @@ -4298,6 +4298,9 @@ TclGetsObjBinary( goto restore; } bufPtr = statePtr->inQueueTail; + if (bufPtr == NULL) { + goto restore; + } } dst = (unsigned char *) RemovePoint(bufPtr); @@ -4410,12 +4413,12 @@ TclGetsObjBinary( restore: bufPtr = statePtr->inQueueHead; - if (bufPtr == NULL) { - Tcl_Panic("TclGetsObjBinary: restore reached with bufPtr==NULL"); + if (bufPtr) { + bufPtr->nextRemoved = oldRemoved; + bufPtr = bufPtr->nextPtr; } - bufPtr->nextRemoved = oldRemoved; - for (bufPtr = bufPtr->nextPtr; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { + for ( ; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bufPtr->nextRemoved = BUFFER_PADDING; } CommonGetsCleanup(chanPtr); @@ -4571,7 +4574,9 @@ FilterInputBytes( bufPtr = statePtr->inQueueTail; gsPtr->bufPtr = bufPtr; if (bufPtr == NULL) { - Tcl_Panic("GetInput nuked buffers!"); + gsPtr->charsWrote = 0; + gsPtr->rawRead = 0; + return -1; } } diff --git a/tests/ioCmd.test b/tests/ioCmd.test index d2c0173..bb133f9 100644 --- a/tests/ioCmd.test +++ b/tests/ioCmd.test @@ -818,10 +818,27 @@ test iocmd-21.23 {[close] in [gets] segfaults} -setup { set ch [chan create read foo] } -body { gets $ch -} -returnCodes error -cleanup { +} -cleanup { catch {close $ch} rename foo {} -} -match glob -result {*invalid argument*} +} -result {} +test iocmd-21.24 {[close] in binary [gets] segfaults} -setup { + proc foo {method chan args} { + switch -- $method initialize { + return {initialize finalize watch read} + } finalize {} watch {} read { + catch {close $chan} + return \n + } + } + set ch [chan create read foo] +} -body { + chan configure $ch -translation binary + gets $ch +} -cleanup { + catch {close $ch} + rename foo {} +} -result {} # --- --- --- --------- --------- --------- # Helper commands to record the arguments to handler methods. -- cgit v0.12 From 10d7a2ac566063ffdd10a932a0d610ae6ecd62dd Mon Sep 17 00:00:00 2001 From: dkf Date: Wed, 30 Apr 2014 21:24:39 +0000 Subject: [82e7f67325] Fix an evil refcount problem in compiled [string replace]. --- generic/tclExecute.c | 14 ++++++++++++-- tests/stringComp.test | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/generic/tclExecute.c b/generic/tclExecute.c index 2c136d7..4ecca5b 100644 --- a/generic/tclExecute.c +++ b/generic/tclExecute.c @@ -5702,11 +5702,21 @@ TEBCresume( length - toIdx); } } else { - objResultPtr = value3Ptr; + /* + * Be careful with splicing the stack in this case; we have a + * refCount:1 object in value3Ptr and we want to append to it and + * make it be the refCount:1 object at the top of the stack + * afterwards. [Bug 82e7f67325] + */ + if (toIdx < length) { - Tcl_AppendUnicodeToObj(objResultPtr, ustring1 + toIdx + 1, + Tcl_AppendUnicodeToObj(value3Ptr, ustring1 + toIdx + 1, length - toIdx); } + TRACE_APPEND(("\"%.30s\"\n", O2S(value3Ptr))); + TclDecrRefCount(valuePtr); + OBJ_AT_TOS = value3Ptr; /* Tricky! */ + NEXT_INST_F(1, 0, 0); } TclDecrRefCount(value3Ptr); TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); diff --git a/tests/stringComp.test b/tests/stringComp.test index 9e00ce7..39dac78 100644 --- a/tests/stringComp.test +++ b/tests/stringComp.test @@ -26,6 +26,22 @@ catch [list package require -exact Tcltest [info patchlevel]] # Some tests require the testobj command testConstraint testobj [expr {[info commands testobj] != {}}] +testConstraint memory [llength [info commands memory]] +if {[testConstraint memory]} { + proc getbytes {} { + set lines [split [memory info] \n] + return [lindex $lines 3 3] + } + proc leaktest {script {iterations 3}} { + set end [getbytes] + for {set i 0} {$i < $iterations} {incr i} { + uplevel 1 $script + set tmp $end + set end [getbytes] + } + return [expr {$end - $tmp}] + } +} test stringComp-1.1 {error conditions} { proc foo {} {string gorp a b} @@ -687,7 +703,23 @@ test stringComp-12.1 {Bug 3588366: end-offsets before start} { ## not yet bc ## string replace -## not yet bc +test stringComp-14.1 {Bug 82e7f67325} { + apply {{} { + set a [join {a b} {}] + lappend b [string length [string replace ___! 0 2 $a]] + lappend b [string length [string replace ___! 0 2 $a[unset a]]] + }} +} {3 3} +test stringComp-14.2 {Bug 82e7f67325} { + # As in stringComp-14.1, but make sure we don't retain too many refs + leaktest { + apply {{} { + set a [join {a b} {}] + lappend b [string length [string replace ___! 0 2 $a]] + lappend b [string length [string replace ___! 0 2 $a[unset a]]] + }} + } +} {0} ## string tolower ## not yet bc -- cgit v0.12 From 7df749abdc0780eb176e6fade94388d60cd8a0ef Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Wed, 30 Apr 2014 21:59:00 +0000 Subject: Better (safer) fix for [0e92c404f1] --- generic/tclCmdMZ.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclCmdMZ.c b/generic/tclCmdMZ.c index 6fd468c..d106f53 100644 --- a/generic/tclCmdMZ.c +++ b/generic/tclCmdMZ.c @@ -2086,7 +2086,7 @@ StringRangeCmd( * Unicode string rep to get the range. */ - if (objv[1]->typePtr == &tclByteArrayType) { + if (objv[1]->typePtr == &tclByteArrayType && (objv[1]->bytes==NULL)) { string = Tcl_GetByteArrayFromObj(objv[1], &length); length--; } else { -- cgit v0.12 From f715c2fad2a69457bbcbdf99167c66a3f62ed3a5 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 1 May 2014 01:15:42 +0000 Subject: missing constraint --- tests/stringComp.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stringComp.test b/tests/stringComp.test index 39dac78..0d134b5 100644 --- a/tests/stringComp.test +++ b/tests/stringComp.test @@ -710,7 +710,7 @@ test stringComp-14.1 {Bug 82e7f67325} { lappend b [string length [string replace ___! 0 2 $a[unset a]]] }} } {3 3} -test stringComp-14.2 {Bug 82e7f67325} { +test stringComp-14.2 {Bug 82e7f67325} memory { # As in stringComp-14.1, but make sure we don't retain too many refs leaktest { apply {{} { -- cgit v0.12 From 1c52941e5f67f7f374dbc110234bf18a7ac4844a Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Thu, 1 May 2014 07:38:27 +0000 Subject: Fix more corner-cases like [0e92c404f19ede5b2eb06e6db27647d3138cc56|0e92c404f1]: The only place where a type of &tclByteArrayType can be trusted is when determining its length, because the character length of a (UTF-8) string is always equal to the byte length of the byte array. --- generic/tclCmdMZ.c | 12 ++++++------ generic/tclExecute.c | 10 +++++----- generic/tclInt.h | 18 ++++++++++++++++++ generic/tclUtil.c | 2 +- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/generic/tclCmdMZ.c b/generic/tclCmdMZ.c index d106f53..70943e9 100644 --- a/generic/tclCmdMZ.c +++ b/generic/tclCmdMZ.c @@ -1345,7 +1345,7 @@ StringIndexCmd( * Unicode string rep to get the index'th char. */ - if (objv[1]->typePtr == &tclByteArrayType) { + if (TclIsPureByteArray(objv[1])) { const unsigned char *string = Tcl_GetByteArrayFromObj(objv[1], &length); @@ -2086,7 +2086,7 @@ StringRangeCmd( * Unicode string rep to get the range. */ - if (objv[1]->typePtr == &tclByteArrayType && (objv[1]->bytes==NULL)) { + if (TclIsPureByteArray(objv[1])) { string = Tcl_GetByteArrayFromObj(objv[1], &length); length--; } else { @@ -2537,8 +2537,8 @@ StringEqualCmd( return TCL_OK; } - if (!nocase && objv[0]->typePtr == &tclByteArrayType && - objv[1]->typePtr == &tclByteArrayType) { + if (!nocase && TclIsPureByteArray(objv[0]) && + TclIsPureByteArray(objv[1])) { /* * Use binary versions of comparisons since that won't cause undue * type conversions and it is much faster. Only do this if we're @@ -2684,8 +2684,8 @@ StringCmpCmd( return TCL_OK; } - if (!nocase && objv[0]->typePtr == &tclByteArrayType && - objv[1]->typePtr == &tclByteArrayType) { + if (!nocase && TclIsPureByteArray(objv[0]) && + TclIsPureByteArray(objv[1])) { /* * Use binary versions of comparisons since that won't cause undue * type conversions and it is much faster. Only do this if we're diff --git a/generic/tclExecute.c b/generic/tclExecute.c index 2e396e8..c4f9836 100644 --- a/generic/tclExecute.c +++ b/generic/tclExecute.c @@ -4235,8 +4235,8 @@ TclExecuteByteCode( */ iResult = s1len = s2len = 0; - } else if ((valuePtr->typePtr == &tclByteArrayType) - && (value2Ptr->typePtr == &tclByteArrayType)) { + } else if (TclIsPureByteArray(valuePtr) + && TclIsPureByteArray(value2Ptr)) { s1 = (char *) Tcl_GetByteArrayFromObj(valuePtr, &s1len); s2 = (char *) Tcl_GetByteArrayFromObj(value2Ptr, &s2len); iResult = memcmp(s1, s2, @@ -4354,7 +4354,7 @@ TclExecuteByteCode( * use the Unicode string rep to get the index'th char. */ - if (valuePtr->typePtr == &tclByteArrayType) { + if (TclIsPureByteArray(valuePtr)) { bytes = (char *)Tcl_GetByteArrayFromObj(valuePtr, &length); } else { /* @@ -4370,7 +4370,7 @@ TclExecuteByteCode( } if ((index >= 0) && (index < length)) { - if (valuePtr->typePtr == &tclByteArrayType) { + if (TclIsPureByteArray(valuePtr)) { objResultPtr = Tcl_NewByteArrayObj((unsigned char *) (&bytes[index]), 1); } else if (valuePtr->bytes && length == valuePtr->length) { @@ -4422,7 +4422,7 @@ TclExecuteByteCode( ustring2 = Tcl_GetUnicodeFromObj(value2Ptr, &length2); match = TclUniCharMatch(ustring1, length1, ustring2, length2, nocase); - } else if ((valuePtr->typePtr == &tclByteArrayType) && !nocase) { + } else if (TclIsPureByteArray(valuePtr) && !nocase) { unsigned char *string1, *string2; int length1, length2; diff --git a/generic/tclInt.h b/generic/tclInt.h index 00b246b..2353450 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -3668,6 +3668,24 @@ MODULE_SCOPE void TclDbInitNewObj(Tcl_Obj *objPtr, CONST char *file, /* *---------------------------------------------------------------- + * Macro that encapsulates the logic that determines when it is safe to + * interpret a string as a byte array directly. In summary, the object must be + * a byte array and must not have a string representation (as the operations + * that it is used in are defined on strings, not byte arrays). Theoretically + * it is possible to also be efficient in the case where the object's bytes + * field is filled by generation from the byte array (c.f. list canonicality) + * but we don't do that at the moment since this is purely about efficiency. + * The ANSI C "prototype" for this macro is: + * + * MODULE_SCOPE int TclIsPureByteArray(Tcl_Obj *objPtr); + *---------------------------------------------------------------- + */ + +#define TclIsPureByteArray(objPtr) \ + (((objPtr)->typePtr==&tclByteArrayType) && ((objPtr)->bytes==NULL)) + +/* + *---------------------------------------------------------------- * Macro used by the Tcl core to compare Unicode strings. On big-endian * systems we can use the more efficient memcmp, but this would not be * lexically correct on little-endian systems. The ANSI C "prototype" for diff --git a/generic/tclUtil.c b/generic/tclUtil.c index 5f4cdae..8c6adfe 100644 --- a/generic/tclUtil.c +++ b/generic/tclUtil.c @@ -2334,7 +2334,7 @@ TclStringMatchObj( udata = Tcl_GetUnicodeFromObj(strObj, &length); uptn = Tcl_GetUnicodeFromObj(ptnObj, &plen); match = TclUniCharMatch(udata, length, uptn, plen, flags); - } else if ((strObj->typePtr == &tclByteArrayType) && !flags) { + } else if (TclIsPureByteArray(strObj) && !flags) { unsigned char *data, *ptn; data = Tcl_GetByteArrayFromObj(strObj, &length); -- cgit v0.12 From f90303fa441e484833044f364aae0974a6a705d4 Mon Sep 17 00:00:00 2001 From: dkf Date: Thu, 1 May 2014 09:11:09 +0000 Subject: make doubly sure that things which should be unshared stay unshared --- tests/stringComp.test | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/stringComp.test b/tests/stringComp.test index 0d134b5..165ef20 100644 --- a/tests/stringComp.test +++ b/tests/stringComp.test @@ -704,20 +704,20 @@ test stringComp-12.1 {Bug 3588366: end-offsets before start} { ## string replace test stringComp-14.1 {Bug 82e7f67325} { - apply {{} { - set a [join {a b} {}] + apply {x { + set a [join $x {}] lappend b [string length [string replace ___! 0 2 $a]] lappend b [string length [string replace ___! 0 2 $a[unset a]]] - }} + }} {a b} } {3 3} test stringComp-14.2 {Bug 82e7f67325} memory { # As in stringComp-14.1, but make sure we don't retain too many refs leaktest { - apply {{} { - set a [join {a b} {}] + apply {x { + set a [join $x {}] lappend b [string length [string replace ___! 0 2 $a]] lappend b [string length [string replace ___! 0 2 $a[unset a]]] - }} + }} {a b} } } {0} -- cgit v0.12 From cf5a2fcdb5b8dd834e530641922f7ba5c841553f Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 1 May 2014 14:18:35 +0000 Subject: We must Preserve channels if we're going to use TclChanCaughtErrorBypass() to get error information after channel routines are called (and have possibly called for the channel to go away). --- generic/tclIOCmd.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/generic/tclIOCmd.c b/generic/tclIOCmd.c index 7e8e91a..b206303 100644 --- a/generic/tclIOCmd.c +++ b/generic/tclIOCmd.c @@ -177,6 +177,7 @@ Tcl_PutsObjCmd( return TCL_ERROR; } + Tcl_Preserve(chan); result = Tcl_WriteObj(chan, string); if (result < 0) { goto error; @@ -187,6 +188,7 @@ Tcl_PutsObjCmd( goto error; } } + Tcl_Release(chan); return TCL_OK; /* @@ -202,6 +204,7 @@ Tcl_PutsObjCmd( TclGetString(chanObjPtr), "\": ", Tcl_PosixError(interp), NULL); } + Tcl_Release(chan); return TCL_ERROR; } @@ -248,6 +251,7 @@ Tcl_FlushObjCmd( return TCL_ERROR; } + Tcl_Preserve(chan); if (Tcl_Flush(chan) != TCL_OK) { /* * TIP #219. @@ -261,8 +265,10 @@ Tcl_FlushObjCmd( TclGetString(chanObjPtr), "\": ", Tcl_PosixError(interp), NULL); } + Tcl_Release(chan); return TCL_ERROR; } + Tcl_Release(chan); return TCL_OK; } @@ -295,6 +301,7 @@ Tcl_GetsObjCmd( int lineLen; /* Length of line just read. */ int mode; /* Mode in which channel is opened. */ Tcl_Obj *linePtr, *chanObjPtr; + int code = TCL_OK; if ((objc != 2) && (objc != 3)) { Tcl_WrongNumArgs(interp, 1, objv, "channelId ?varName?"); @@ -310,6 +317,7 @@ Tcl_GetsObjCmd( return TCL_ERROR; } + Tcl_Preserve(chan); linePtr = Tcl_NewObj(); lineLen = Tcl_GetsObj(chan, linePtr); if (lineLen < 0) { @@ -329,7 +337,8 @@ Tcl_GetsObjCmd( TclGetString(chanObjPtr), "\": ", Tcl_PosixError(interp), NULL); } - return TCL_ERROR; + code = TCL_ERROR; + goto done; } lineLen = -1; } @@ -339,11 +348,12 @@ Tcl_GetsObjCmd( return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj(lineLen)); - return TCL_OK; } else { Tcl_SetObjResult(interp, linePtr); } - return TCL_OK; + done: + Tcl_Release(chan); + return code; } /* @@ -542,6 +552,7 @@ Tcl_SeekObjCmd( mode = modeArray[optionIndex]; } + Tcl_Preserve(chan); result = Tcl_Seek(chan, offset, mode); if (result == Tcl_LongAsWide(-1)) { /* @@ -555,8 +566,10 @@ Tcl_SeekObjCmd( TclGetString(objv[1]), "\": ", Tcl_PosixError(interp), NULL); } + Tcl_Release(chan); return TCL_ERROR; } + Tcl_Release(chan); return TCL_OK; } @@ -587,6 +600,7 @@ Tcl_TellObjCmd( { Tcl_Channel chan; /* The channel to tell on. */ Tcl_WideInt newLoc; + int code; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channelId"); @@ -602,6 +616,7 @@ Tcl_TellObjCmd( return TCL_ERROR; } + Tcl_Preserve(chan); newLoc = Tcl_Tell(chan); /* @@ -610,7 +625,10 @@ Tcl_TellObjCmd( * them into the regular interpreter result. */ - if (TclChanCaughtErrorBypass(interp, chan)) { + + code = TclChanCaughtErrorBypass(interp, chan); + Tcl_Release(chan); + if (code) { return TCL_ERROR; } -- cgit v0.12 From b18161555e63f857014d2306adcb9fbcad3c6144 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 1 May 2014 16:33:39 +0000 Subject: Stop the segfault in iogt-2.4. First by changing the UpdateInterest() call that triggers it. "downChanPtr" may no longer be the right argument at that point. Second, after ending the segfault, the test became an infinite loop (nested unstacking?! whoa.), so revised the test to one that terminates (and passes). Left behind a comment that the recursive unstacking case may require more examination. --- generic/tclIO.c | 9 ++++++++- tests/iogt.test | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 776ff12..a83cdcd 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -1876,6 +1876,13 @@ Tcl_UnstackChannel( * into the old structure. */ + /* + * TODO: Figure out how to handle the situation where the chan + * operations called below by this unstacking operation cause + * another unstacking recursively. In that case the downChanPtr + * value we're holding on to will not be the right thing. + */ + Channel *downChanPtr = chanPtr->downChanPtr; /* @@ -1980,7 +1987,7 @@ Tcl_UnstackChannel( */ Tcl_EventuallyFree(chanPtr, TCL_DYNAMIC); - UpdateInterest(downChanPtr); + UpdateInterest(statePtr->topChanPtr); if (result != 0) { Tcl_SetErrno(result); diff --git a/tests/iogt.test b/tests/iogt.test index bd3c67b..ded8bb9 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -228,7 +228,7 @@ proc id_torture {chan op data} { delete/read - clear_read {;#ignore} flush/write - - flush/read - + flush/read {} write - read { testchannel unstack $chan -- cgit v0.12 From 4f1714013f16d9993d2d68175d81fdb91ffc8190 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 2 May 2014 12:39:14 +0000 Subject: Re-enable buffer recycling. --- generic/tclIO.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index a83cdcd..8ae2fd2 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2360,7 +2360,7 @@ RecycleBuffer( mustDiscard = 1; } - if (1 || mustDiscard) { + if (mustDiscard) { ReleaseChannelBuffer(bufPtr); return; } -- cgit v0.12 From 3452d681c93ec5cab5edc2d45bbd0d02f9beadb1 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 2 May 2014 13:02:48 +0000 Subject: Fully restore topChan resetting to accommodate self-restacking channels. --- generic/tclIO.c | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 8ae2fd2..adea32e 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4554,9 +4554,12 @@ Tcl_GetsObj( * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ - /* - chanPtr = statePtr->topChanPtr; - */ + + if (chanPtr != statePtr->topChanPtr) { + Tcl_Release(chanPtr); + chanPtr = statePtr->topChanPtr; + Tcl_Preserve(chanPtr); + } bufPtr = gs.bufPtr; if (bufPtr == NULL) { @@ -4590,9 +4593,11 @@ Tcl_GetsObj( * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ - /* - chanPtr = statePtr->topChanPtr; - */ + if (chanPtr != statePtr->topChanPtr) { + Tcl_Release(chanPtr); + chanPtr = statePtr->topChanPtr; + Tcl_Preserve(chanPtr); + } bufPtr = statePtr->inQueueHead; if (bufPtr != NULL) { bufPtr->nextRemoved = oldRemoved; @@ -4632,9 +4637,11 @@ Tcl_GetsObj( * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ - /* - chanPtr = statePtr->topChanPtr; - */ + if (chanPtr != statePtr->topChanPtr) { + Tcl_Release(chanPtr); + chanPtr = statePtr->topChanPtr; + Tcl_Preserve(chanPtr); + } UpdateInterest(chanPtr); Tcl_Release(chanPtr); return copiedTotal; @@ -5638,11 +5645,11 @@ DoReadChars( ResetFlag(statePtr, CHANNEL_BLOCKED); } result = GetInput(chanPtr); -if (chanPtr != statePtr->topChanPtr) { -Tcl_Release(chanPtr); -chanPtr = statePtr->topChanPtr; -Tcl_Preserve(chanPtr); -} + if (chanPtr != statePtr->topChanPtr) { + Tcl_Release(chanPtr); + chanPtr = statePtr->topChanPtr; + Tcl_Preserve(chanPtr); + } if (result != 0) { if (result == EAGAIN) { break; @@ -5673,9 +5680,11 @@ Tcl_Preserve(chanPtr); * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ - /* - chanPtr = statePtr->topChanPtr; - */ + if (chanPtr != statePtr->topChanPtr) { + Tcl_Release(chanPtr); + chanPtr = statePtr->topChanPtr; + Tcl_Preserve(chanPtr); + } UpdateInterest(chanPtr); Tcl_Release(chanPtr); return copied; -- cgit v0.12 From ab2c4a52f1dbcc67939ad86233d21cf7fc38a5cd Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 2 May 2014 14:45:03 +0000 Subject: Add some comments about possible other self-restacking troubles. --- generic/tclIO.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index adea32e..58c7b3c 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -1747,6 +1747,10 @@ Tcl_StackChannel( statePtr->csPtrR = NULL; statePtr->csPtrW = NULL; + /* + * TODO: Examine what can go wrong if Tcl_Flush() call disturbs + * the stacking state of this channel during its operations. + */ if (Tcl_Flush((Tcl_Channel) prevChanPtr) != TCL_OK) { statePtr->csPtrR = csPtrR; statePtr->csPtrW = csPtrW; @@ -9786,12 +9790,15 @@ StackSetBlockMode( { int result = 0; Tcl_DriverBlockModeProc *blockModeProc; + ChannelState *statePtr = chanPtr->state; /* * Start at the top of the channel stack + * TODO: Examine what can go wrong when blockModeProc calls + * disturb the stacking state of the channel. */ - chanPtr = chanPtr->state->topChanPtr; + chanPtr = statePtr->topChanPtr; while (chanPtr != NULL) { blockModeProc = Tcl_ChannelBlockModeProc(chanPtr->typePtr); if (blockModeProc != NULL) { -- cgit v0.12 From 60ed1a9051fbb233b229facc524d7a2aed49a18b Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 2 May 2014 15:19:57 +0000 Subject: Fixup restacking tests to expect the right results. --- tests/ioTrans.test | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ioTrans.test b/tests/ioTrans.test index 3bbd170..7f4f7f0 100644 --- a/tests/ioTrans.test +++ b/tests/ioTrans.test @@ -539,7 +539,7 @@ test iortrans-4.8 {chan read, read, bug 2921116} -setup { tempdone rename foo {} } -result {{read rt* {test data -}} file*} +}} {}} test iortrans-4.8.1 {chan read, bug 721ec69271} -setup { set res {} } -match glob -body { @@ -557,8 +557,8 @@ test iortrans-4.8.1 {chan read, bug 721ec69271} -setup { } -cleanup { tempdone rename foo {} -} -result {{read rt* {test data -}} file*} +} -result {{read rt* te} {read rt* st} {read rt* { d}} {read rt* at} {read rt* {a +}} {}} test iortrans-4.8.2 {chan read, bug 721ec69271} -setup { set res {} } -match glob -body { @@ -577,8 +577,8 @@ test iortrans-4.8.2 {chan read, bug 721ec69271} -setup { } -cleanup { tempdone rename foo {} -} -result {{read rt* {test data -}} file*} +} -result {{read rt* t} {read rt* e} {read rt* s} {read rt* t} {read rt* { }} {read rt* d} {read rt* a} {read rt* t} {read rt* a} {read rt* { +}} {}} test iortrans-4.9 {chan read, gets, bug 2921116} -setup { set res {} } -match glob -body { @@ -596,7 +596,7 @@ test iortrans-4.9 {chan read, gets, bug 2921116} -setup { tempdone rename foo {} } -result {{read rt* {test data -}} file*} +}} {}} # --- === *** ########################### # method write (via puts) -- cgit v0.12 From 4a18366f17a69027323e8f2c479aab89a9f6ae81 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Fri, 2 May 2014 15:35:14 +0000 Subject: Re-apply [3010352], bringing back the symbol exports of shared libraries as it was in 8.6.0/8.6.1. --- generic/tcl.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/generic/tcl.h b/generic/tcl.h index b93b3ac..e557290 100644 --- a/generic/tcl.h +++ b/generic/tcl.h @@ -2433,9 +2433,15 @@ EXTERN void Tcl_GetMemoryInfo(Tcl_DString *dsPtr); /* * Include platform specific public function declarations that are accessible - * via the stubs table. + * via the stubs table. Make all TclOO symbols MODULE_SCOPE (which only + * has effect on building it as a shared library). See ticket [3010352]. */ +#if defined(BUILD_tcl) +# undef TCLAPI +# define TCLAPI MODULE_SCOPE +#endif + #include "tclPlatDecls.h" /* -- cgit v0.12 From b0f443a7d7b38c2220cfb1b1c0710a804f55c811 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 5 May 2014 09:17:46 +0000 Subject: Backport "GotFlag" macro from Tcl 8.6. Makes code more readable. No change in functionality. --- generic/tclIO.c | 186 +++++++++++++++++++++++++++----------------------------- 1 file changed, 89 insertions(+), 97 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 46a17f5..2f652e7 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -298,6 +298,7 @@ static int WillRead(Channel *chanPtr); #define SetFlag(statePtr, flag) ((statePtr)->flags |= (flag)) #define ResetFlag(statePtr, flag) ((statePtr)->flags &= ~(flag)) +#define GotFlag(statePtr, flag) ((statePtr)->flags & (flag)) /* * Macro for testing whether a string (in optionName, length len) matches a @@ -463,7 +464,7 @@ TclFinalizeIOSubsystem(void) statePtr != NULL; statePtr = statePtr->nextCSPtr) { chanPtr = statePtr->topChanPtr; - if (!(statePtr->flags & (CHANNEL_INCLOSE|CHANNEL_CLOSED|CHANNEL_DEAD))) { + if (!GotFlag(statePtr, CHANNEL_INCLOSE|CHANNEL_CLOSED|CHANNEL_DEAD)) { active = 1; break; } @@ -560,6 +561,7 @@ Tcl_SetStdChannel( int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + switch (type) { case TCL_STDIN: tsdPtr->stdinInitialized = 1; @@ -676,11 +678,9 @@ Tcl_CreateCloseHandler( ClientData clientData) /* Arbitrary data to pass to the close * callback. */ { - ChannelState *statePtr; + ChannelState *statePtr = ((Channel *) chan)->state; CloseCallback *cbPtr; - statePtr = ((Channel *) chan)->state; - cbPtr = (CloseCallback *) ckalloc(sizeof(CloseCallback)); cbPtr->proc = proc; cbPtr->clientData = clientData; @@ -716,10 +716,9 @@ Tcl_DeleteCloseHandler( ClientData clientData) /* The callback data for the callback to * remove. */ { - ChannelState *statePtr; + ChannelState *statePtr = ((Channel *) chan)->state; CloseCallback *cbPtr, *cbPrevPtr; - statePtr = ((Channel *) chan)->state; for (cbPtr = statePtr->closeCbPtr, cbPrevPtr = NULL; cbPtr != NULL; cbPtr = cbPtr->nextPtr) { if ((cbPtr->proc == proc) && (cbPtr->clientData == clientData)) { @@ -873,7 +872,7 @@ DeleteChannelTable( SetFlag(statePtr, CHANNEL_TAINTED); statePtr->refCount--; if (statePtr->refCount <= 0) { - if (!(statePtr->flags & BG_FLUSH_SCHEDULED)) { + if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { (void) Tcl_Close(interp, (Tcl_Channel) chanPtr); } } @@ -1064,7 +1063,7 @@ Tcl_UnregisterChannel( statePtr = ((Channel *) chan)->state->bottomChanPtr->state; - if (statePtr->flags & CHANNEL_INCLOSE) { + if (GotFlag(statePtr, CHANNEL_INCLOSE)) { if (interp != NULL) { Tcl_AppendResult(interp, "Illegal recursive call to close " "through close-handler of channel", NULL); @@ -1103,12 +1102,12 @@ Tcl_UnregisterChannel( SetFlag(statePtr, BUFFER_READY); } Tcl_Preserve((ClientData)statePtr); - if (!(statePtr->flags & BG_FLUSH_SCHEDULED)) { + if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { /* * We don't want to re-enter Tcl_Close(). */ - if (!(statePtr->flags & CHANNEL_CLOSED)) { + if (!GotFlag(statePtr, CHANNEL_CLOSED)) { if (Tcl_Close(interp, chan) != TCL_OK) { SetFlag(statePtr, CHANNEL_CLOSED); Tcl_Release((ClientData)statePtr); @@ -1317,7 +1316,7 @@ Tcl_GetChannel( chanPtr = Tcl_GetHashValue(hPtr); chanPtr = chanPtr->state->bottomChanPtr; if (modePtr != NULL) { - *modePtr = (chanPtr->state->flags & (TCL_READABLE|TCL_WRITABLE)); + *modePtr = chanPtr->state->flags & (TCL_READABLE|TCL_WRITABLE); } return (Tcl_Channel) chanPtr; @@ -1365,7 +1364,7 @@ TclGetChannelFromObj( *channelPtr = (Tcl_Channel) (statePtr->bottomChanPtr); if (modePtr != NULL) { - *modePtr = (statePtr->flags & (TCL_READABLE|TCL_WRITABLE)); + *modePtr = statePtr->flags & (TCL_READABLE|TCL_WRITABLE); } return TCL_OK; @@ -1645,13 +1644,10 @@ Tcl_StackChannel( */ if ((mask & TCL_WRITABLE) != 0) { - CopyState *csPtrR; - CopyState *csPtrW; + CopyState *csPtrR = statePtr->csPtrR; + CopyState *csPtrW = statePtr->csPtrW; - csPtrR = statePtr->csPtrR; statePtr->csPtrR = NULL; - - csPtrW = statePtr->csPtrW; statePtr->csPtrW = NULL; /* @@ -1807,14 +1803,11 @@ Tcl_UnstackChannel( * CheckForChannelErrors inside. */ - if (statePtr->flags & TCL_WRITABLE) { - CopyState *csPtrR; - CopyState *csPtrW; + if (GotFlag(statePtr, TCL_WRITABLE)) { + CopyState *csPtrR = statePtr->csPtrR; + CopyState *csPtrW = statePtr->csPtrW; - csPtrR = statePtr->csPtrR; statePtr->csPtrR = NULL; - - csPtrW = statePtr->csPtrW; statePtr->csPtrW = NULL; if (Tcl_Flush((Tcl_Channel) chanPtr) != TCL_OK) { @@ -1851,16 +1844,14 @@ Tcl_UnstackChannel( * 'DiscardInputQueued' on that. */ - if ((((statePtr->flags & TCL_READABLE) != 0)) && + if (GotFlag(statePtr, TCL_READABLE) && ((statePtr->inQueueHead != NULL) || (chanPtr->inQueueHead != NULL))) { - if ((statePtr->inQueueHead != NULL) && (chanPtr->inQueueHead != NULL)) { statePtr->inQueueTail->nextPtr = chanPtr->inQueueHead; statePtr->inQueueTail = chanPtr->inQueueTail; statePtr->inQueueHead = statePtr->inQueueTail; - } else if (chanPtr->inQueueHead != NULL) { statePtr->inQueueHead = chanPtr->inQueueHead; statePtr->inQueueTail = chanPtr->inQueueTail; @@ -2312,7 +2303,7 @@ RecycleBuffer( * Only save buffers for the input queue if the channel is readable. */ - if (statePtr->flags & TCL_READABLE) { + if (GotFlag(statePtr, TCL_READABLE)) { if (statePtr->inQueueHead == NULL) { statePtr->inQueueHead = bufPtr; statePtr->inQueueTail = bufPtr; @@ -2328,7 +2319,7 @@ RecycleBuffer( * Only save buffers for the output queue if the channel is writable. */ - if (statePtr->flags & TCL_WRITABLE) { + if (GotFlag(statePtr, TCL_WRITABLE)) { if (statePtr->curOutPtr == NULL) { statePtr->curOutPtr = bufPtr; goto keepBuffer; @@ -2401,7 +2392,7 @@ CheckForDeadChannel( Tcl_Interp *interp, /* For error reporting (can be NULL) */ ChannelState *statePtr) /* The channel state to check. */ { - if (statePtr->flags & CHANNEL_DEAD) { + if (GotFlag(statePtr, CHANNEL_DEAD)) { Tcl_SetErrno(EINVAL); if (interp) { Tcl_AppendResult(interp, @@ -2477,7 +2468,7 @@ FlushChannel( if (((statePtr->curOutPtr != NULL) && IsBufferFull(statePtr->curOutPtr)) - || ((statePtr->flags & BUFFER_READY) && + || (GotFlag(statePtr, BUFFER_READY) && (statePtr->outQueueHead == NULL))) { ResetFlag(statePtr, BUFFER_READY); statePtr->curOutPtr->nextPtr = NULL; @@ -2496,8 +2487,7 @@ FlushChannel( * is active, we just return without producing any output. */ - if ((!calledFromAsyncFlush) && - (statePtr->flags & BG_FLUSH_SCHEDULED)) { + if (!calledFromAsyncFlush && GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { return 0; } @@ -2551,7 +2541,7 @@ FlushChannel( * it's a tty channel (dup'ed underneath) */ - if (!(statePtr->flags & BG_FLUSH_SCHEDULED)) { + if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { SetFlag(statePtr, BG_FLUSH_SCHEDULED); UpdateInterest(chanPtr); } @@ -2651,7 +2641,7 @@ FlushChannel( * data has been flushed at the system level. */ - if (statePtr->flags & BG_FLUSH_SCHEDULED) { + if (GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { if (wroteSome) { return errorCode; } else if (statePtr->outQueueHead == NULL) { @@ -2667,7 +2657,7 @@ FlushChannel( * current output buffer. */ - if ((statePtr->flags & CHANNEL_CLOSED) && (statePtr->refCount <= 0) && + if (GotFlag(statePtr, CHANNEL_CLOSED) && (statePtr->refCount <= 0) && (statePtr->outQueueHead == NULL) && ((statePtr->curOutPtr == NULL) || IsBufferEmpty(statePtr->curOutPtr))) { @@ -2743,7 +2733,7 @@ CloseChannel( * device. */ - if ((statePtr->outEofChar != 0) && (statePtr->flags & TCL_WRITABLE)) { + if ((statePtr->outEofChar != 0) && GotFlag(statePtr, TCL_WRITABLE)) { int dummy; char c = (char) statePtr->outEofChar; @@ -3148,7 +3138,7 @@ Tcl_Close( Tcl_Panic("called Tcl_Close on channel with refCount > 0"); } - if (statePtr->flags & CHANNEL_INCLOSE) { + if (GotFlag(statePtr, CHANNEL_INCLOSE)) { if (interp) { Tcl_AppendResult(interp, "Illegal recursive call to close " "through close-handler of channel", NULL); @@ -3673,7 +3663,7 @@ Write( endEncoding = ((statePtr->outputEncodingFlags & TCL_ENCODING_END) != 0); - if ((statePtr->flags & CHANNEL_LINEBUFFERED) + if (GotFlag(statePtr, CHANNEL_LINEBUFFERED) || (statePtr->outputTranslation != TCL_TRANSLATE_LF)) { nextNewLine = memchr(src, '\n', srcLen); } @@ -3803,8 +3793,8 @@ Write( } ReleaseChannelBuffer(bufPtr); } - if ((flushed < total) && (statePtr->flags & CHANNEL_UNBUFFERED || - (needNlFlush && statePtr->flags & CHANNEL_LINEBUFFERED))) { + if ((flushed < total) && (GotFlag(statePtr, CHANNEL_UNBUFFERED) || + (needNlFlush && GotFlag(statePtr, CHANNEL_LINEBUFFERED)))) { SetFlag(statePtr, BUFFER_READY); if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; @@ -4049,7 +4039,7 @@ Tcl_GetsObj( case TCL_TRANSLATE_AUTO: eol = dst; skip = 1; - if (statePtr->flags & INPUT_SAW_CR) { + if (GotFlag(statePtr, INPUT_SAW_CR)) { ResetFlag(statePtr, INPUT_SAW_CR); if ((eol < dstEnd) && (*eol == '\n')) { /* @@ -4117,7 +4107,7 @@ Tcl_GetsObj( SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; } - if (statePtr->flags & CHANNEL_EOF) { + if (GotFlag(statePtr, CHANNEL_EOF)) { skip = 0; eol = dstEnd; if (eol == objPtr->bytes + oldLength) { @@ -4328,8 +4318,8 @@ TclGetsObjBinary( * device. Side effect is to allocate another channel buffer. */ - if (statePtr->flags & CHANNEL_BLOCKED) { - if (statePtr->flags & CHANNEL_NONBLOCKING) { + if (GotFlag(statePtr, CHANNEL_BLOCKED)) { + if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { goto restore; } ResetFlag(statePtr, CHANNEL_BLOCKED); @@ -4383,7 +4373,7 @@ TclGetsObjBinary( SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; } - if (statePtr->flags & CHANNEL_EOF) { + if (GotFlag(statePtr, CHANNEL_EOF)) { skip = 0; eol = dstEnd; if ((dst == dstEnd) && (byteLen == oldLength)) { @@ -4598,8 +4588,8 @@ FilterInputBytes( */ read: - if (statePtr->flags & CHANNEL_BLOCKED) { - if (statePtr->flags & CHANNEL_NONBLOCKING) { + if (GotFlag(statePtr, CHANNEL_BLOCKED)) { + if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { gsPtr->charsWrote = 0; gsPtr->rawRead = 0; return -1; @@ -4684,7 +4674,7 @@ FilterInputBytes( * returning those UTF-8 characters because a EOL might be * present in them. */ - } else if (statePtr->flags & CHANNEL_EOF) { + } else if (GotFlag(statePtr, CHANNEL_EOF)) { /* * There was a partial character followed by EOF on the * device. Fall through, returning that nothing was found. @@ -4706,7 +4696,7 @@ FilterInputBytes( statePtr->inQueueTail = nextPtr; } extra = rawLen - gsPtr->rawRead; - memcpy(nextPtr->buf + BUFFER_PADDING - extra, + memcpy(nextPtr->buf + (BUFFER_PADDING - extra), raw + gsPtr->rawRead, (size_t) extra); nextPtr->nextRemoved -= extra; bufPtr->nextAdded -= extra; @@ -4773,7 +4763,7 @@ PeekAhead( goto cleanup; } - if ((statePtr->flags & CHANNEL_NONBLOCKING) == 0) { + if (!GotFlag(statePtr, CHANNEL_NONBLOCKING)) { blockModeProc = Tcl_ChannelBlockModeProc(chanPtr->typePtr); if (blockModeProc == NULL) { /* @@ -4970,11 +4960,11 @@ Tcl_ReadRaw( copiedNow = CopyBuffer(chanPtr, bufPtr + copied, bytesToRead - copied); if (copiedNow == 0) { - if (statePtr->flags & CHANNEL_EOF) { + if (GotFlag(statePtr, CHANNEL_EOF)) { goto done; } - if (statePtr->flags & CHANNEL_BLOCKED) { - if (statePtr->flags & CHANNEL_NONBLOCKING) { + if (GotFlag(statePtr, CHANNEL_BLOCKED)) { + if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { goto done; } ResetFlag(statePtr, CHANNEL_BLOCKED); @@ -4988,9 +4978,9 @@ Tcl_ReadRaw( * and only if we are sure to have data. */ - if ((statePtr->flags & CHANNEL_NONBLOCKING) && + if (GotFlag(statePtr, CHANNEL_NONBLOCKING) && (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !(statePtr->flags & CHANNEL_HAS_MORE_DATA)) { + !GotFlag(statePtr, CHANNEL_HAS_MORE_DATA)) { /* * We bypass the driver; it would block as no data is * available. @@ -5231,11 +5221,11 @@ DoReadChars( } if (copiedNow < 0) { - if (statePtr->flags & CHANNEL_EOF) { + if (GotFlag(statePtr, CHANNEL_EOF)) { break; } - if (statePtr->flags & CHANNEL_BLOCKED) { - if (statePtr->flags & CHANNEL_NONBLOCKING) { + if (GotFlag(statePtr, CHANNEL_BLOCKED)) { + if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { break; } ResetFlag(statePtr, CHANNEL_BLOCKED); @@ -5362,7 +5352,7 @@ ReadBytes( } dst += offset; - if (statePtr->flags & INPUT_NEED_NL) { + if (GotFlag(statePtr, INPUT_NEED_NL)) { ResetFlag(statePtr, INPUT_NEED_NL); if ((srcLen == 0) || (*src != '\n')) { *dst = '\r'; @@ -5543,7 +5533,7 @@ ReadChars( } oldState = statePtr->inputEncodingState; - if (statePtr->flags & INPUT_NEED_NL) { + if (GotFlag(statePtr, INPUT_NEED_NL)) { /* * We want a '\n' because the last character we saw was '\r'. */ @@ -5809,7 +5799,7 @@ TranslateInputEOL( srcEnd = srcStart + dstLen; srcMax = srcStart + *srcLenPtr; - if ((statePtr->flags & INPUT_SAW_CR) && (src < srcMax)) { + if (GotFlag(statePtr, INPUT_SAW_CR) && (src < srcMax)) { if (*src == '\n') { src++; } @@ -5914,7 +5904,7 @@ Tcl_Ungets( * bit. We want to discover these conditions anew in each operation. */ - if (statePtr->flags & CHANNEL_STICKY_EOF) { + if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { goto done; } ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_EOF); @@ -6171,7 +6161,7 @@ GetInput( * platforms it is impossible to read from a device after EOF. */ - if (statePtr->flags & CHANNEL_EOF) { + if (GotFlag(statePtr, CHANNEL_EOF)) { return 0; } @@ -6183,9 +6173,9 @@ GetInput( * sure to have data. */ - if ((statePtr->flags & CHANNEL_NONBLOCKING) && + if (GotFlag(statePtr, CHANNEL_NONBLOCKING) && (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !(statePtr->flags & CHANNEL_HAS_MORE_DATA)) { + !GotFlag(statePtr, CHANNEL_HAS_MORE_DATA)) { /* * Bypass the driver, it would block, as no data is available */ @@ -6355,14 +6345,14 @@ Tcl_Seek( */ wasAsync = 0; - if (statePtr->flags & CHANNEL_NONBLOCKING) { + if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { wasAsync = 1; result = StackSetBlockMode(chanPtr, TCL_MODE_BLOCKING); if (result != 0) { return Tcl_LongAsWide(-1); } ResetFlag(statePtr, CHANNEL_NONBLOCKING); - if (statePtr->flags & BG_FLUSH_SCHEDULED) { + if (GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { ResetFlag(statePtr, BG_FLUSH_SCHEDULED); } } @@ -6698,8 +6688,7 @@ CheckChannelErrors( * order to drain data from stacked channels. */ - if ((statePtr->flags & CHANNEL_CLOSED) && - ((flags & CHANNEL_RAW_MODE) == 0)) { + if (GotFlag(statePtr, CHANNEL_CLOSED) && !(flags & CHANNEL_RAW_MODE)) { Tcl_SetErrno(EACCES); return -1; } @@ -6721,7 +6710,7 @@ CheckChannelErrors( * retrieving and transforming the data to copy. */ - if (BUSY_STATE(statePtr,flags) && ((flags & CHANNEL_RAW_MODE) == 0)) { + if (BUSY_STATE(statePtr, flags) && ((flags & CHANNEL_RAW_MODE) == 0)) { Tcl_SetErrno(EBUSY); return -1; } @@ -6734,7 +6723,7 @@ CheckChannelErrors( * discover these conditions anew in each operation. */ - if ((statePtr->flags & CHANNEL_STICKY_EOF) == 0) { + if (!GotFlag(statePtr, CHANNEL_STICKY_EOF)) { ResetFlag(statePtr, CHANNEL_EOF); } ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_NEED_MORE_DATA); @@ -6766,8 +6755,8 @@ Tcl_Eof( ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ - return ((statePtr->flags & CHANNEL_STICKY_EOF) || - ((statePtr->flags & CHANNEL_EOF) && + return (GotFlag(statePtr, CHANNEL_STICKY_EOF) || + (GotFlag(statePtr, CHANNEL_EOF) && (Tcl_InputBuffered(chan) == 0))) ? 1 : 0; } @@ -6794,7 +6783,7 @@ Tcl_InputBlocked( ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ - return (statePtr->flags & CHANNEL_BLOCKED) ? 1 : 0; + return GotFlag(statePtr, CHANNEL_BLOCKED) ? 1 : 0; } /* @@ -7437,10 +7426,10 @@ Tcl_SetChannelOption( ckfree((char *) argv); return TCL_ERROR; } - if (statePtr->flags & TCL_READABLE) { + if (GotFlag(statePtr, TCL_READABLE)) { statePtr->inEofChar = inValue; } - if (statePtr->flags & TCL_WRITABLE) { + if (GotFlag(statePtr, TCL_WRITABLE)) { statePtr->outEofChar = outValue; } } else { @@ -7474,11 +7463,11 @@ Tcl_SetChannelOption( } if (argc == 1) { - readMode = (statePtr->flags & TCL_READABLE) ? argv[0] : NULL; - writeMode = (statePtr->flags & TCL_WRITABLE) ? argv[0] : NULL; + readMode = GotFlag(statePtr, TCL_READABLE) ? argv[0] : NULL; + writeMode = GotFlag(statePtr, TCL_WRITABLE) ? argv[0] : NULL; } else if (argc == 2) { - readMode = (statePtr->flags & TCL_READABLE) ? argv[0] : NULL; - writeMode = (statePtr->flags & TCL_WRITABLE) ? argv[1] : NULL; + readMode = GotFlag(statePtr, TCL_READABLE) ? argv[0] : NULL; + writeMode = GotFlag(statePtr, TCL_WRITABLE) ? argv[1] : NULL; } else { if (interp) { Tcl_AppendResult(interp, @@ -7694,9 +7683,9 @@ Tcl_NotifyChannel( */ if ((mask & TCL_READABLE) && - (statePtr->flags & CHANNEL_NONBLOCKING) && + GotFlag(statePtr, CHANNEL_NONBLOCKING) && (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !(statePtr->flags & CHANNEL_TIMER_FEV)) { + !GotFlag(statePtr, CHANNEL_TIMER_FEV)) { SetFlag(statePtr, CHANNEL_HAS_MORE_DATA); } #endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ @@ -7759,7 +7748,7 @@ Tcl_NotifyChannel( * don't call any write handlers before the flush is complete. */ - if ((statePtr->flags & BG_FLUSH_SCHEDULED) && (mask & TCL_WRITABLE)) { + if (GotFlag(statePtr, BG_FLUSH_SCHEDULED) && (mask & TCL_WRITABLE)) { FlushChannel(NULL, chanPtr, 1); mask &= ~TCL_WRITABLE; } @@ -7839,7 +7828,7 @@ UpdateInterest( * watch for the channel to become writable. */ - if (statePtr->flags & BG_FLUSH_SCHEDULED) { + if (GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { mask |= TCL_WRITABLE; } @@ -7851,7 +7840,7 @@ UpdateInterest( */ if (mask & TCL_READABLE) { - if (!(statePtr->flags & CHANNEL_NEED_MORE_DATA) + if (!GotFlag(statePtr, CHANNEL_NEED_MORE_DATA) && (statePtr->inQueueHead != NULL) && IsBufferReady(statePtr->inQueueHead)) { mask &= ~TCL_READABLE; @@ -7930,7 +7919,7 @@ ChannelTimerProc( ChannelState *statePtr = chanPtr->state; /* State info for channel */ - if (!(statePtr->flags & CHANNEL_NEED_MORE_DATA) + if (!GotFlag(statePtr, CHANNEL_NEED_MORE_DATA) && (statePtr->interestMask & TCL_READABLE) && (statePtr->inQueueHead != NULL) && IsBufferReady(statePtr->inQueueHead)) { @@ -7950,14 +7939,14 @@ ChannelTimerProc( * similar test is done in "PeekAhead". */ - if ((statePtr->flags & CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL)) { + if (GotFlag(statePtr, CHANNEL_NONBLOCKING) && + (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL)) { SetFlag(statePtr, CHANNEL_TIMER_FEV); } #endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ Tcl_Preserve(statePtr); - Tcl_NotifyChannel((Tcl_Channel)chanPtr, TCL_READABLE); + Tcl_NotifyChannel((Tcl_Channel) chanPtr, TCL_READABLE); #ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING ResetFlag(statePtr, CHANNEL_TIMER_FEV); @@ -8920,7 +8909,7 @@ DoRead( */ Tcl_Preserve(chanPtr); - if (!(statePtr->flags & CHANNEL_STICKY_EOF)) { + if (!GotFlag(statePtr, CHANNEL_STICKY_EOF)) { ResetFlag(statePtr, CHANNEL_EOF); } ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_NEED_MORE_DATA); @@ -8929,11 +8918,11 @@ DoRead( copiedNow = CopyAndTranslateBuffer(statePtr, bufPtr + copied, toRead - copied); if (copiedNow == 0) { - if (statePtr->flags & CHANNEL_EOF) { + if (GotFlag(statePtr, CHANNEL_EOF)) { goto done; } - if (statePtr->flags & CHANNEL_BLOCKED) { - if (statePtr->flags & CHANNEL_NONBLOCKING) { + if (GotFlag(statePtr, CHANNEL_BLOCKED)) { + if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { goto done; } ResetFlag(statePtr, CHANNEL_BLOCKED); @@ -9088,7 +9077,7 @@ CopyAndTranslateBuffer( curByte = *src; if (curByte == '\n') { ResetFlag(statePtr, INPUT_SAW_CR); - } else if (statePtr->flags & INPUT_SAW_CR) { + } else if (GotFlag(statePtr, INPUT_SAW_CR)) { ResetFlag(statePtr, INPUT_SAW_CR); *dst = '\r'; dst++; @@ -9131,7 +9120,7 @@ CopyAndTranslateBuffer( *dst = '\n'; dst++; } else { - if ((curByte != '\n') || !(statePtr->flags & INPUT_SAW_CR)) { + if ((curByte != '\n') || !GotFlag(statePtr, INPUT_SAW_CR)) { *dst = (char) curByte; dst++; } @@ -10551,8 +10540,9 @@ SetChannelFromAny( * The channel is valid until any call to DetachChannel occurs. * Ensure consistency checks are done. */ - statePtr = GET_CHANNELSTATE(objPtr); - if (statePtr->flags & (CHANNEL_TAINTED|CHANNEL_CLOSED)) { + + statePtr = GET_CHANNELSTATE(objPtr); + if (GotFlag(statePtr, CHANNEL_TAINTED|CHANNEL_CLOSED)) { ResetFlag(statePtr, CHANNEL_TAINTED); Tcl_Release((ClientData) statePtr); objPtr->typePtr = NULL; @@ -10650,5 +10640,7 @@ DumpFlags( * mode: c * c-basic-offset: 4 * fill-column: 78 + * tab-width: 8 + * indent-tabs-mode: nil * End: */ -- cgit v0.12 From 7d27109da91753ce0ed031d0c7c3eb095b0b7fed Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 5 May 2014 17:18:51 +0000 Subject: Segfaulting test (backport of iortrans-5.11). --- tests/iogt.test | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/iogt.test b/tests/iogt.test index d81acd6..abe4246 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -251,7 +251,17 @@ proc id_torture {chan op data} { clear_read {;#ignore} flush/write - flush/read {} - write - + write { + global level + if {$level} { + return + } + incr level + testchannel unstack $chan + testchannel transform $chan \ + -command [namespace code [list id_torture $chan]] + return $data + } read { testchannel unstack $chan testchannel transform $chan \ @@ -665,6 +675,16 @@ test iogt-2.4 {basic I/O, mixed trail} {testchannel} { close $fh set x } {} +test iogt-2.5 {basic I/O, mixed trail} {testchannel} { + set ::level 0 + set fh [open $path(dummyout) w] + torture -attach $fh + puts -nonewline $fh abcdef + flush $fh + testchannel unstack $fh + close $fh + set x +} {} test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} \ {testchannel unknownFailure} { -- cgit v0.12 From d9866626fadb0a1c94f0317d07cb7dcb5dfd7c86 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Tue, 6 May 2014 11:17:28 +0000 Subject: Start working on [3389978]. Appears to work, but some clean-up needed. --- tests/winFCmd.test | 4 ++-- win/tclWinFile.c | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/tests/winFCmd.test b/tests/winFCmd.test index bd50328..28257c6 100644 --- a/tests/winFCmd.test +++ b/tests/winFCmd.test @@ -1385,10 +1385,10 @@ test winFCmd-19.5 {Windows extended path names} -constraints nt -setup { list [catch { set f [open $tmpfile [list WRONLY CREAT]] close $f - } res] errormsg ;#$res + } res] $res } -cleanup { catch {file delete $tmpfile} -} -result [list 1 errormsg] +} -result [list 0 {}] test winFCmd-19.6 {Windows extended path names} -constraints nt -setup { set tmpfile [file join $::env(TEMP) tcl[string repeat x 248].tmp] set tmpfile //?/[file normalize $tmpfile] diff --git a/win/tclWinFile.c b/win/tclWinFile.c index fc0ac9e..e32cd94 100644 --- a/win/tclWinFile.c +++ b/win/tclWinFile.c @@ -2930,8 +2930,36 @@ TclNativeCreateNativeRep( Tcl_WinUtfToTChar(str, len, &ds); len = Tcl_DStringLength(&ds) + sizeof(WCHAR); wp = (WCHAR *) Tcl_DStringValue(&ds); - for (i=sizeof(WCHAR); i|", *wp) ){ + i=sizeof(WCHAR); + if ((wp[0]=='/'||wp[0]=='\\') && (wp[1]=='/'||wp[1]=='\\')) { + if (wp[2]=='?'){ + /* Extended path prefix: convert slashes but not the '?' */ + wp[0] = wp[1] = wp[3] = '\\'; + i += 8; wp+=4; + if (((wp[0]>='A'&&wp[0]<='Z') || (wp[0]>='a'&&wp[0]<='z')) + && (wp[1]==':') && (wp[2]=='/' || wp[2]=='\\')) { + /* With drive, don't convert the ':' */ + i += 4; wp+=2; + } + } + } else { + if (((wp[0]>='A'&&wp[0]<='Z') || (wp[0]>='a'&&wp[0]<='z')) + && (wp[1]==':') && (wp[2]=='/' || wp[2]=='\\')) { + /* With drive, don't convert the ':' */ + i += 4; wp+=2; + if (len > (MAX_PATH * sizeof(WCHAR))){ + /* Path is too long, needs an extended path prefix. */ + Tcl_DStringSetLength(&ds, len+=8); + Tcl_DStringSetLength(&ds, len+1); /* Must end with two NUL bytes */ + wp = (WCHAR *) Tcl_DStringValue(&ds); /* wp might be re-allocated */ + memmove(wp+4, wp, len-8); + memcpy(wp, L"\\\\?\\", 8); + i+=12; wp += 6; + } + } + } + for (; i?|", *wp) ){ if (!*wp){ /* See bug [3118489]: NUL in filenames */ Tcl_DecrRefCount(validPathPtr); -- cgit v0.12 From 0fc3bdcc7e6bd2903a1e84017073bf39dff8a3ad Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 6 May 2014 13:56:29 +0000 Subject: Have to manage the lifetime of the self handle in testchannel transform. --- generic/tclIOGT.c | 15 +++++++++++++-- tests/iogt.test | 1 - 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/generic/tclIOGT.c b/generic/tclIOGT.c index beddf4f..c5372b1 100644 --- a/generic/tclIOGT.c +++ b/generic/tclIOGT.c @@ -298,7 +298,6 @@ TclChannelTransform( } Tcl_DStringFree(&ds); - dataPtr->self = chan; dataPtr->watchMask = 0; dataPtr->mode = mode; dataPtr->timer = NULL; @@ -317,6 +316,7 @@ TclChannelTransform( ReleaseData(dataPtr); return TCL_ERROR; } + Tcl_Preserve(dataPtr->self); /* * At last initialize the transformation at the script level. @@ -437,6 +437,9 @@ ExecuteCallback( break; case TRANSMIT_DOWN: + if (dataPtr->self == NULL) { + break; + } resObj = Tcl_GetObjResult(eval); resBuf = Tcl_GetByteArrayFromObj(resObj, &resLen); Tcl_WriteRaw(Tcl_GetStackedChannel(dataPtr->self), (char *) resBuf, @@ -444,6 +447,9 @@ ExecuteCallback( break; case TRANSMIT_SELF: + if (dataPtr->self == NULL) { + break; + } resObj = Tcl_GetObjResult(eval); resBuf = Tcl_GetByteArrayFromObj(resObj, &resLen); Tcl_WriteRaw(dataPtr->self, (char *) resBuf, resLen); @@ -579,6 +585,8 @@ TransformCloseProc( * General cleanup. */ + Tcl_Release(dataPtr->self); + dataPtr->self = NULL; ReleaseData(dataPtr); return TCL_OK; } @@ -614,7 +622,7 @@ TransformInputProc( * Should assert(dataPtr->mode & TCL_READABLE); */ - if (toRead == 0) { + if (toRead == 0 || dataPtr->self == NULL) { /* * Catch a no-op. */ @@ -1083,6 +1091,9 @@ TransformWatchProc( * unchanged. */ + if (dataPtr->self == NULL) { + return; + } downChan = Tcl_GetStackedChannel(dataPtr->self); Tcl_GetChannelType(downChan)->watchProc( diff --git a/tests/iogt.test b/tests/iogt.test index abe4246..d4291b3 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -683,7 +683,6 @@ test iogt-2.5 {basic I/O, mixed trail} {testchannel} { flush $fh testchannel unstack $fh close $fh - set x } {} test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} \ -- cgit v0.12 From e3ee9f9e15df60c47593c2b50c17fefef1909e1d Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 6 May 2014 15:23:43 +0000 Subject: Add Panic call to better identify where iogt-2.5 goes wrong. --- generic/tclIO.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index 58c7b3c..6bf28a1 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2311,6 +2311,9 @@ static void PreserveChannelBuffer( ChannelBuffer *bufPtr) { + if (bufPtr->refCount == 0) { + Tcl_Panic("Reuse of ChannelBuffer!"); + } bufPtr->refCount++; } -- cgit v0.12 From 6c0d722a0a6753769d3f933a724a37667f176638 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 6 May 2014 17:33:55 +0000 Subject: Symptom relief. Make test stop panicking. This is not the proper final answer. ChannelBuffer management in FlushChannel is simply not robustly correct yet. --- generic/tclIO.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 6bf28a1..dd4d489 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2713,8 +2713,11 @@ FlushChannel( statePtr->outQueueTail = NULL; } RecycleBuffer(statePtr, bufPtr, 0); + bufPtr = NULL; + } + if (bufPtr) { + ReleaseChannelBuffer(bufPtr); } - ReleaseChannelBuffer(bufPtr); } /* Closes "while (1)". */ /* -- cgit v0.12 From e35a33106d2918d82903fa94e973f42fd5f28a1d Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 6 May 2014 22:34:45 +0000 Subject: Stop memory leak in io-29.27. --- generic/tclIO.c | 1 + 1 file changed, 1 insertion(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index 2f652e7..fc3722c 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2613,6 +2613,7 @@ FlushChannel( */ DiscardOutputQueued(statePtr); + ReleaseChannelBuffer(bufPtr); continue; } else { wroteSome = 1; -- cgit v0.12 From 06da97fda1d8384b5700cf28a14998a15ca94c0a Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 6 May 2014 23:23:43 +0000 Subject: Stop memory leak in io-29.34 --- generic/tclIO.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index fc3722c..b2b4e5c 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2525,6 +2525,7 @@ FlushChannel( if (errorCode == EINTR) { errorCode = 0; + ReleaseChannelBuffer(bufPtr); continue; } @@ -2546,6 +2547,7 @@ FlushChannel( UpdateInterest(chanPtr); } errorCode = 0; + ReleaseChannelBuffer(bufPtr); break; } -- cgit v0.12 From d9de529a706559caa6f3cadab3c54249eca3c8d9 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 6 May 2014 23:51:32 +0000 Subject: Stop leak in io-33.7. --- generic/tclIOCmd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generic/tclIOCmd.c b/generic/tclIOCmd.c index b206303..afd6272 100644 --- a/generic/tclIOCmd.c +++ b/generic/tclIOCmd.c @@ -345,7 +345,8 @@ Tcl_GetsObjCmd( if (objc == 3) { if (Tcl_ObjSetVar2(interp, objv[2], NULL, linePtr, TCL_LEAVE_ERR_MSG) == NULL) { - return TCL_ERROR; + code = TCL_ERROR; + goto done; } Tcl_SetObjResult(interp, Tcl_NewIntObj(lineLen)); } else { -- cgit v0.12 From 5c31837bdf02ca496a7764d983b7186c62228026 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 7 May 2014 02:28:56 +0000 Subject: Stop leak in io-53.5. --- generic/tclIO.c | 1 + 1 file changed, 1 insertion(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index b2b4e5c..f2d1f44 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -3787,6 +3787,7 @@ Write( if (IsBufferFull(bufPtr)) { if (FlushChannel(NULL, chanPtr, 0) != 0) { + ReleaseChannelBuffer(bufPtr); return -1; } flushed += statePtr->bufSize; -- cgit v0.12 From 228272365c7aec6154a3468e75f99981152c7a3c Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 7 May 2014 03:30:55 +0000 Subject: Stop leaks of cloned Tcl_ChannelTypes. --- generic/tclIORChan.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/generic/tclIORChan.c b/generic/tclIORChan.c index 17c1593..7630473 100644 --- a/generic/tclIORChan.c +++ b/generic/tclIORChan.c @@ -1019,6 +1019,7 @@ ReflectClose( Tcl_Obj *resObj; /* Result data for 'close' */ ReflectedChannelMap* rcmPtr; /* Map of reflected channels with handlers in this interp */ Tcl_HashEntry* hPtr; /* Entry in the above map */ + Tcl_ChannelType *tctPtr; if (TclInThreadExit()) { /* @@ -1055,6 +1056,11 @@ ReflectClose( } #endif + tctPtr = ((Channel *)rcPtr->chan)->typePtr; + if (tctPtr && tctPtr != &tclRChannelType) { + ckfree((char *)tctPtr); + ((Channel *)rcPtr->chan)->typePtr = NULL; + } Tcl_EventuallyFree (rcPtr, (Tcl_FreeProc *) FreeReflectedChannel); return EOK; } @@ -1118,6 +1124,11 @@ ReflectClose( } #endif + tctPtr = ((Channel *)rcPtr->chan)->typePtr; + if (tctPtr && tctPtr != &tclRChannelType) { + ckfree((char *)tctPtr); + ((Channel *)rcPtr->chan)->typePtr = NULL; + } Tcl_EventuallyFree (rcPtr, (Tcl_FreeProc *) FreeReflectedChannel); #ifdef TCL_THREADS } @@ -2033,13 +2044,6 @@ FreeReflectedChannel( { Channel *chanPtr = (Channel *) rcPtr->chan; - if (chanPtr->typePtr != &tclRChannelType) { - /* - * Delete a cloned ChannelType structure. - */ - - ckfree((char*) chanPtr->typePtr); - } Tcl_Release(chanPtr); Tcl_DecrRefCount(rcPtr->name); Tcl_DecrRefCount(rcPtr->methods); @@ -2698,11 +2702,13 @@ ForwardProc( * call upon for the driver. */ - case ForwardedClose: + case ForwardedClose: { /* * No parameters/results. */ + Tcl_ChannelType *tctPtr; + if (InvokeTclMethod(rcPtr, METH_FINAL, NULL, NULL, &resObj)!=TCL_OK) { ForwardSetObjError(paramPtr, resObj); } @@ -2727,8 +2733,14 @@ ForwardProc( Tcl_GetChannelName (rcPtr->chan)); Tcl_DeleteHashEntry (hPtr); + tctPtr = ((Channel *)rcPtr->chan)->typePtr; + if (tctPtr && tctPtr != &tclRChannelType) { + ckfree((char *)tctPtr); + ((Channel *)rcPtr->chan)->typePtr = NULL; + } Tcl_EventuallyFree (rcPtr, (Tcl_FreeProc *) FreeReflectedChannel); break; + } case ForwardedInput: { Tcl_Obj *toReadObj = Tcl_NewIntObj(paramPtr->input.toRead); -- cgit v0.12 From 7c1d853a93ed3c14acece6af7b054bed1a22b67b Mon Sep 17 00:00:00 2001 From: dkf Date: Wed, 7 May 2014 22:19:30 +0000 Subject: Corrected description of where tcl_platform(user) comes from on Unix. --- doc/tclvars.n | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tclvars.n b/doc/tclvars.n index b3e1bee..84823e0 100644 --- a/doc/tclvars.n +++ b/doc/tclvars.n @@ -284,8 +284,8 @@ was compiled with threads enabled. \fBuser\fR This identifies the current user based on the login information available on the platform. -This comes from the USER or LOGNAME environment variable on Unix, -and the value from GetUserName on Windows. +This value comes from the getuid() and getpwuid() system calls on Unix, +and the value from the GetUserName() system call on Windows. .TP \fBwordSize\fR This gives the size of the native-machine word in bytes (strictly, it -- cgit v0.12 From b622ae7f2419b531a196cd2fe0ac570f195bf030 Mon Sep 17 00:00:00 2001 From: dkf Date: Wed, 7 May 2014 22:22:02 +0000 Subject: Corrected description of where tcl_platform(user) comes from on Unix. --- doc/tclvars.n | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tclvars.n b/doc/tclvars.n index 9d7a4ce..48ab83a 100644 --- a/doc/tclvars.n +++ b/doc/tclvars.n @@ -347,8 +347,8 @@ was compiled with threads enabled. . This identifies the current user based on the login information available on the platform. -This comes from the USER or LOGNAME environment variable on Unix, -and the value from GetUserName on Windows. +This value comes from the getuid() and getpwuid() system calls on Unix, +and the value from the GetUserName() system call on Windows. .TP \fBwordSize\fR . -- cgit v0.12 From 48ae7e42c1bd6a104c78d737fd6b826a1a4ee7bd Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 8 May 2014 02:38:52 +0000 Subject: Stop leak in iocmd-21.22. --- generic/tclIO.c | 1 + 1 file changed, 1 insertion(+) diff --git a/generic/tclIO.c b/generic/tclIO.c index f2d1f44..4628e90 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -3607,6 +3607,7 @@ static int WillRead(Channel *chanPtr) { if (chanPtr->typePtr == NULL) { /* Prevent read attempts on a closed channel */ + DiscardInputQueued(chanPtr->state, 0); Tcl_SetErrno(EINVAL); return -1; } -- cgit v0.12 From 435b75fc24a85e806029ac4159863e6bf87b6412 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Thu, 8 May 2014 12:46:47 +0000 Subject: More efficient/robust implementation of function TclNativeCreateNativeRep(). - No more intermediate results in a Tcl_DString, just allocate space directly. - Let MultiByteToWideChar() do all the difficult work, inclusive checking for invalid byte sequences. - Handled extended win32 paths, inclusive UNC paths. Implementation for a great deal taken over from fossil. --- win/tclWinFile.c | 109 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/win/tclWinFile.c b/win/tclWinFile.c index e32cd94..5761eeb 100644 --- a/win/tclWinFile.c +++ b/win/tclWinFile.c @@ -2897,10 +2897,10 @@ ClientData TclNativeCreateNativeRep( Tcl_Obj *pathPtr) { - char *nativePathPtr, *str; - Tcl_DString ds; + WCHAR *nativePathPtr; + const char *str; Tcl_Obj *validPathPtr; - int len, i = 2; + int len; WCHAR *wp; if (TclFSCwdIsNative()) { @@ -2927,55 +2927,72 @@ TclNativeCreateNativeRep( } str = Tcl_GetStringFromObj(validPathPtr, &len); - Tcl_WinUtfToTChar(str, len, &ds); - len = Tcl_DStringLength(&ds) + sizeof(WCHAR); - wp = (WCHAR *) Tcl_DStringValue(&ds); - i=sizeof(WCHAR); - if ((wp[0]=='/'||wp[0]=='\\') && (wp[1]=='/'||wp[1]=='\\')) { - if (wp[2]=='?'){ - /* Extended path prefix: convert slashes but not the '?' */ - wp[0] = wp[1] = wp[3] = '\\'; - i += 8; wp+=4; - if (((wp[0]>='A'&&wp[0]<='Z') || (wp[0]>='a'&&wp[0]<='z')) - && (wp[1]==':') && (wp[2]=='/' || wp[2]=='\\')) { - /* With drive, don't convert the ':' */ - i += 4; wp+=2; - } - } - } else { - if (((wp[0]>='A'&&wp[0]<='Z') || (wp[0]>='a'&&wp[0]<='z')) - && (wp[1]==':') && (wp[2]=='/' || wp[2]=='\\')) { - /* With drive, don't convert the ':' */ - i += 4; wp+=2; - if (len > (MAX_PATH * sizeof(WCHAR))){ - /* Path is too long, needs an extended path prefix. */ - Tcl_DStringSetLength(&ds, len+=8); - Tcl_DStringSetLength(&ds, len+1); /* Must end with two NUL bytes */ - wp = (WCHAR *) Tcl_DStringValue(&ds); /* wp might be re-allocated */ - memmove(wp+4, wp, len-8); - memcpy(wp, L"\\\\?\\", 8); - i+=12; wp += 6; - } + + if (strlen(str)!=len) { + /* String contains NUL-bytes. This is invalid. */ + return 0; + } + /* Let MultiByteToWideChar check for other invalid sequences, like + * 0xC0 0x80 (== overlong NUL). See bug [3118489]: NUL in filenames */ + len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, 0, 0); + if (len==0) { + return 0; + } + /* Overallocate 6 chars, making some room for extended paths */ + wp = nativePathPtr = ckalloc( (len+6) * sizeof(WCHAR) ); + if (nativePathPtr==0) { + return 0; + } + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, nativePathPtr, len); + /* + ** If path starts with "//?/" or "\\?\" (extended path), translate + ** any slashes to backslashes but leave the '?' intact + */ + if ((str[0]=='\\' || str[0]=='/') && (str[1]=='\\' || str[1]=='/') + && str[2]=='?' && (str[3]=='\\' || str[3]=='/')) { + wp[0] = wp[1] = wp[3] = '\\'; + str += 4; + wp += 4; + } + /* + ** If there is no "\\?\" prefix but there is a drive or UNC + ** path prefix and the path is larger than MAX_PATH chars, + ** no Win32 API function can handle that unless it is + ** prefixed with the extended path prefix. See: + ** + **/ + if (((str[0]>='A'&&str[0]<='Z') || (str[0]>='a'&&str[0]<='z')) + && str[1]==':' && (str[2]=='\\' || str[2]=='/')) { + if (wp==nativePathPtr && len>MAX_PATH) { + memmove(wp+4, wp, len*sizeof(WCHAR)); + memcpy(wp, L"\\\\?\\", 4*sizeof(WCHAR)); + wp += 4; } + /* + ** If (remainder of) path starts with ":/" or ":\", + ** leave the ':' intact but translate the backslash to a slash. + */ + wp[2] = '\\'; + wp += 3; + } else if (wp==nativePathPtr && len>MAX_PATH + && (str[0]=='\\' || str[0]=='/') + && (str[1]=='\\' || str[1]=='/') && str[2]!='?') { + memmove(wp+6, wp, len*sizeof(WCHAR)); + memcpy(wp, L"\\\\?\\UNC", 7*sizeof(WCHAR)); + wp += 7; } - for (; i?|", *wp) ){ - if (!*wp){ - /* See bug [3118489]: NUL in filenames */ - Tcl_DecrRefCount(validPathPtr); - Tcl_DStringFree(&ds); - return NULL; - } + /* + ** In the remainder of the path, translate invalid characters to + ** characters in the Unicode private use area. + */ + while (*wp != '\0') { + if ((*wp < ' ') || wcschr(L"\"*:<>?|", *wp)) { *wp |= 0xF000; - }else if (*wp=='/') { + } else if (*wp == '/') { *wp = '\\'; } + ++wp; } - Tcl_DecrRefCount(validPathPtr); - nativePathPtr = ckalloc(len); - memcpy(nativePathPtr, Tcl_DStringValue(&ds), (size_t) len); - - Tcl_DStringFree(&ds); return nativePathPtr; } -- cgit v0.12 From 882794a48ef4c92e617a0c6bec02c14ec64d63cf Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 8 May 2014 13:01:47 +0000 Subject: Revert the iogt-2.5 fix. For now one panic is better than widespread memory leaks. --- generic/tclIO.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index d69a3d9..678dc4b 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2716,11 +2716,8 @@ FlushChannel( statePtr->outQueueTail = NULL; } RecycleBuffer(statePtr, bufPtr, 0); - bufPtr = NULL; - } - if (bufPtr) { - ReleaseChannelBuffer(bufPtr); } + ReleaseChannelBuffer(bufPtr); } /* Closes "while (1)". */ /* -- cgit v0.12 From 4f9f25fc55b73b0eb82e118bb35b6e41ce173a27 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 8 May 2014 16:03:50 +0000 Subject: Fix the panic in iogt-2.5. Back in 2011, Bugs 3384654 and 3393276 first noticed troubles with ChannelBuffer sharing, but the magnitude of the problem wasn't truly grasped. A fix was applied that turned out to be more of a band-aid workaround. Now that the real fix is in place, the band-aid is actually preventing it working properly in thie case. Rip it off! --- generic/tclIO.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 678dc4b..8dd9218 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2312,7 +2312,7 @@ PreserveChannelBuffer( ChannelBuffer *bufPtr) { if (bufPtr->refCount == 0) { - Tcl_Panic("Reuse of ChannelBuffer!"); + Tcl_Panic("Reuse of ChannelBuffer! %p", bufPtr); } bufPtr->refCount++; } @@ -2702,9 +2702,7 @@ FlushChannel( wroteSome = 1; } - if (!IsBufferEmpty(bufPtr)) { - bufPtr->nextRemoved += written; - } + bufPtr->nextRemoved += written; /* * If this buffer is now empty, recycle it. -- cgit v0.12 From 501a64ea84e7ed50d4b814d335b41475cc535754 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 8 May 2014 16:21:12 +0000 Subject: silence compiler warning --- generic/tclIORChan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generic/tclIORChan.c b/generic/tclIORChan.c index 12fa4a0..0b462c4 100644 --- a/generic/tclIORChan.c +++ b/generic/tclIORChan.c @@ -1111,7 +1111,7 @@ ReflectClose( ReflectedChannelMap *rcmPtr;/* Map of reflected channels with handlers in * this interp */ Tcl_HashEntry *hPtr; /* Entry in the above map */ - Tcl_ChannelType *tctPtr; + const Tcl_ChannelType *tctPtr; if (TclInThreadExit()) { /* @@ -2881,7 +2881,7 @@ ForwardProc( * No parameters/results. */ - Tcl_ChannelType *tctPtr; + const Tcl_ChannelType *tctPtr; if (InvokeTclMethod(rcPtr, METH_FINAL, NULL, NULL, &resObj)!=TCL_OK) { ForwardSetObjError(paramPtr, resObj); -- cgit v0.12 From 777fc1f403ce2dd7dcf46cc42a059b0fe6363882 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Fri, 9 May 2014 10:08:56 +0000 Subject: Make Cygwin's "configure" work from another directory than /unix. (Not everything works this way!) --- unix/configure | 4 ++-- unix/tcl.m4 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/unix/configure b/unix/configure index 02a3725..d268647 100755 --- a/unix/configure +++ b/unix/configure @@ -7010,9 +7010,9 @@ echo "$as_me: error: CYGWIN compile is only supported with --enable-threads" >&2 fi do64bit_ok=yes if test "x${SHARED_BUILD}" = "x1"; then - echo "running cd ../win; ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args" + echo "running cd ${TCL_SRC_DIR}/win; ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args" # The eval makes quoting arguments work. - if cd ../win; eval ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args; cd ../unix + if cd ${TCL_SRC_DIR}/win; eval ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args; cd ../unix then : else { echo "configure: error: configure failed for ../win" 1>&2; exit 1; } diff --git a/unix/tcl.m4 b/unix/tcl.m4 index 10408a8..b6c86b6 100644 --- a/unix/tcl.m4 +++ b/unix/tcl.m4 @@ -1266,9 +1266,9 @@ AC_DEFUN([SC_CONFIG_CFLAGS], [ fi do64bit_ok=yes if test "x${SHARED_BUILD}" = "x1"; then - echo "running cd ../win; ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args" + echo "running cd ${TCL_SRC_DIR}/win; ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args" # The eval makes quoting arguments work. - if cd ../win; eval ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args; cd ../unix + if cd ${TCL_SRC_DIR}/win; eval ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args; cd ../unix then : else { echo "configure: error: configure failed for ../win" 1>&2; exit 1; } -- cgit v0.12 From d28769d37874fb207bec2ac0d3c8206c7ab566f8 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 9 May 2014 13:19:30 +0000 Subject: Test iocmd-32.1 is not "impossible" but after writing it properly it does segfault trying to use a deleted interp. Fixed. --- generic/tclIORChan.c | 12 +++++++++++- tests/ioCmd.test | 9 ++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/generic/tclIORChan.c b/generic/tclIORChan.c index 7630473..3107f9e 100644 --- a/generic/tclIORChan.c +++ b/generic/tclIORChan.c @@ -438,8 +438,8 @@ static const char *msg_write_nothing = "{write wrote nothing}"; static const char *msg_seek_beforestart = "{Tried to seek before origin}"; #ifdef TCL_THREADS static const char *msg_send_originlost = "{Channel thread lost}"; -static const char *msg_send_dstlost = "{Owner lost}"; #endif /* TCL_THREADS */ +static const char *msg_send_dstlost = "{Owner lost}"; static const char *msg_dstlost = "-code 1 -level 0 -errorcode NONE -errorinfo {} -errorline 1 {Owner lost}"; /* @@ -1302,6 +1302,7 @@ ReflectOutput( /* ASSERT: rcPtr->mode & TCL_WRITABLE */ Tcl_Preserve(rcPtr); + Tcl_Preserve(rcPtr->interp); bufObj = Tcl_NewByteArrayObj((unsigned char *) buf, toWrite); Tcl_IncrRefCount(bufObj); @@ -1318,6 +1319,14 @@ ReflectOutput( goto invalid; } + if (Tcl_InterpDeleted(rcPtr->interp)) { + /* + * The interp was destroyed during InvokeTclMethod(). + */ + + SetChannelErrorStr(rcPtr->chan, msg_send_dstlost); + goto invalid; + } if (Tcl_GetIntFromObj(rcPtr->interp, resObj, &written) != TCL_OK) { Tcl_SetChannelError(rcPtr->chan, MarshallError(rcPtr->interp)); goto invalid; @@ -1347,6 +1356,7 @@ ReflectOutput( stop: Tcl_DecrRefCount(bufObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ + Tcl_Release(rcPtr->interp); Tcl_Release(rcPtr); return written; invalid: diff --git a/tests/ioCmd.test b/tests/ioCmd.test index bb133f9..5a76d48 100644 --- a/tests/ioCmd.test +++ b/tests/ioCmd.test @@ -2038,13 +2038,13 @@ test iocmd-32.1 {origin interpreter of moved channel destroyed during access} -m proc foo {args} { oninit; onfinal; track; # destroy interpreter during channel access - # Actually not possible for an interp to destroy itself. - interp delete {} - return} + suicide + } set chan [chan create {r w} foo] fconfigure $chan -buffering none set chan }] + interp alias $ida suicide {} interp delete $ida # Move channel to 2nd thread. interp eval $ida [list testchannel cut $chan] @@ -2063,8 +2063,7 @@ test iocmd-32.1 {origin interpreter of moved channel destroyed during access} -m set res }] set res -} -constraints {testchannel impossible} \ - -result {Owner lost} +} -constraints {testchannel} -result {Owner lost} test iocmd-32.2 {delete interp of reflected chan} { # Bug 3034840 -- cgit v0.12 From de5e889e3d84eb644ed791aea29fdb8e47972943 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 9 May 2014 16:31:06 +0000 Subject: Tests (chan)io-34.21 are constrained for largefileSupport, and that's disabled by default, which means never tested, which means the ridiculous bugs in them are never found and fixed. Fixed the bugs, changed the default. Large File Suppport (4GB) is commonplace now. Let those without it suffer a few failing tests reporting that fact to them. --- tests/chanio.test | 6 +++--- tests/io.test | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/chanio.test b/tests/chanio.test index b195f7b..5ea7ec1 100644 --- a/tests/chanio.test +++ b/tests/chanio.test @@ -41,7 +41,7 @@ namespace eval ::tcl::test::io { # You need a *very* special environment to do some tests. In # particular, many file systems do not support large-files... - testConstraint largefileSupport 0 + testConstraint largefileSupport 1 # some tests can only be run is umask is 2 # if "umask" cannot be run, the tests will be skipped. @@ -4427,10 +4427,10 @@ test chan-io-34.21 {Tcl_Seek and Tcl_Tell on large files} {largefileSupport} { chan puts -nonewline $f abcdef lappend l [chan tell $f] chan close $f - lappend l [file size $f] + lappend l [file size $path(test3)] # truncate... chan close [open $path(test3) w] - lappend l [file size $f] + lappend l [file size $path(test3)] set l } {0 6 6 4294967296 4294967302 4294967302 0} diff --git a/tests/io.test b/tests/io.test index 7707a28..7f1a357 100644 --- a/tests/io.test +++ b/tests/io.test @@ -41,7 +41,7 @@ testConstraint testthread [llength [info commands testthread]] # You need a *very* special environment to do some tests. In # particular, many file systems do not support large-files... -testConstraint largefileSupport 0 +testConstraint largefileSupport 1 # some tests can only be run is umask is 2 # if "umask" cannot be run, the tests will be skipped. @@ -4433,10 +4433,10 @@ test io-34.21 {Tcl_Seek and Tcl_Tell on large files} {largefileSupport} { puts -nonewline $f abcdef lappend l [tell $f] close $f - lappend l [file size $f] + lappend l [file size $path(test3)] # truncate... close [open $path(test3) w] - lappend l [file size $f] + lappend l [file size $path(test3)] set l } {0 6 6 4294967296 4294967302 4294967302 0} -- cgit v0.12 From f008e78ace0135efe56f81d05155be120472df7e Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 9 May 2014 16:38:23 +0000 Subject: Added comment explaining the "knownBug" in iogt-6.1 --- tests/iogt.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/iogt.test b/tests/iogt.test index d4291b3..20d7538 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -968,6 +968,15 @@ test iogt-6.0 {Push back} testchannel { } {xxx} test iogt-6.1 {Push back and up} {testchannel knownBug} { + + # This test demonstrates the bug/misfeature in the stacked + # channel implementation that data can be discarded if it is + # read into the buffers of one channel in the stack, and then + # that channel is popped before anything above it reads. + # + # This bug can be worked around by always setting -buffersize + # to 1, but who wants to do that? + set f [open $path(dummy) r] # contents of dummy = "abcdefghi..." -- cgit v0.12 From 463d816181eb0f936be39e8bdd6a651b0ad9bd78 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 9 May 2014 16:55:34 +0000 Subject: Correct namespace bugs in normally skipped tests. Constrain them as "knownBug" rather than "unknownFailure". --- tests/iogt.test | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/iogt.test b/tests/iogt.test index 20d7538..0e2eb3c 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -159,8 +159,8 @@ proc fevent {fdelay idelay blocks script data} { #puts stdout ">>>>>" ; flush stdout - uplevel #0 set sock $sk - set res [uplevel #0 $script] + uplevel 1 set sock $sk + set res [uplevel 1 $script] catch {close $sk} return $res @@ -686,7 +686,7 @@ test iogt-2.5 {basic I/O, mixed trail} {testchannel} { } {} test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} \ - {testchannel unknownFailure} { + {testchannel knownBug} { # This test to check the validity of aquired Tcl_Channel references is # not possible because even a backgrounded fcopy will immediately start # to copy data, without waiting for the event loop. This is done only in @@ -703,6 +703,7 @@ test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} \ set fin [open $path(dummy) r] fevent 1000 500 {20 20 20 10 1 1} { + variable copy close $fin set fout [open dummyout w] @@ -740,7 +741,7 @@ test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} \ } {1 {create/write create/read write flush/write flush/read delete/write delete/read}} -test iogt-4.0 {fileevent readable, after transform} {testchannel unknownFailure} { +test iogt-4.0 {fileevent readable, after transform} {testchannel knownBug} { set fin [open $path(dummy) r] set data [read $fin] close $fin @@ -770,10 +771,11 @@ test iogt-4.0 {fileevent readable, after transform} {testchannel unknownFailure} } fevent 1000 500 {20 20 20 10 1} { + variable stop audit_flow trail -attach $sock rblocks_t rbuf trail 23 -attach $sock - fileevent $sock readable [list Get $sock] + fileevent $sock readable [namespace code [list Get $sock]] flush $sock ; # now, or fcopy will error us out # But the 1 second delay should be enough to @@ -871,7 +873,7 @@ delete/write {} *ignored* delete/read {} *ignored*} ; # catch unescaped quote " -test iogt-5.0 {EOF simulation} {testchannel unknownFailure} { +test iogt-5.0 {EOF simulation} {testchannel knownBug} { set fin [open $path(dummy) r] set fout [open $path(dummyout) w] -- cgit v0.12 From f3283e67a3037b34b0a3811bab9e09333c13d8f4 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 9 May 2014 17:46:45 +0000 Subject: Repair another "impossible" test and the segfault it reveals. --- generic/tclIORTrans.c | 2 ++ tests/ioTrans.test | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/generic/tclIORTrans.c b/generic/tclIORTrans.c index 1de635f..1dff4b3 100644 --- a/generic/tclIORTrans.c +++ b/generic/tclIORTrans.c @@ -2010,6 +2010,7 @@ InvokeTclMethod( sr = Tcl_SaveInterpState(rtPtr->interp, 0 /* Dummy */); Tcl_Preserve(rtPtr); + Tcl_Preserve(rtPtr->interp); result = Tcl_EvalObjv(rtPtr->interp, cmdc, rtPtr->argv, TCL_EVAL_GLOBAL); /* @@ -2054,6 +2055,7 @@ InvokeTclMethod( Tcl_IncrRefCount(resObj); } Tcl_RestoreInterpState(rtPtr->interp, sr); + Tcl_Release(rtPtr->interp); Tcl_Release(rtPtr); /* diff --git a/tests/ioTrans.test b/tests/ioTrans.test index 7f4f7f0..c40621b 100644 --- a/tests/ioTrans.test +++ b/tests/ioTrans.test @@ -1034,7 +1034,7 @@ test iortrans-11.1 {origin interpreter of moved transform destroyed during acces # Magic to get the test* commands in the slaves load {} Tcltest $ida load {} Tcltest $idb -} -constraints {testchannel impossible} -match glob -body { +} -constraints {testchannel} -match glob -body { # Set up channel in thread set chan [interp eval $ida $helperscript] set chan [interp eval $ida { @@ -1042,14 +1042,14 @@ test iortrans-11.1 {origin interpreter of moved transform destroyed during acces handle.initialize clear drain flush limit? read write handle.finalize lappend ::res $args - # Destroy interpreter during channel access. Actually not - # possible for an interp to destroy itself. - interp delete {} - return} + # Destroy interpreter during channel access. + suicide + } set chan [chan push [tempchan] foo] fconfigure $chan -buffering none set chan }] + interp alias $ida suicide {} interp delete $ida # Move channel to 2nd thread, transform goes with it. interp eval $ida [list testchannel cut $chan] interp eval $idb [list testchannel splice $chan] -- cgit v0.12 From f779f4219c9a9bdc0c7cd766fbb0e9564936bdd9 Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 11 May 2014 10:39:14 +0000 Subject: [6d2f249a01] Handle a failure to comprehend half-way through the compilation of a chain of compileable ensembles. --- generic/tclEnsemble.c | 24 +++++++++++++++++------- tests/namespace.test | 4 ++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/generic/tclEnsemble.c b/generic/tclEnsemble.c index 9bb7a0c..022dafa 100644 --- a/generic/tclEnsemble.c +++ b/generic/tclEnsemble.c @@ -2751,13 +2751,6 @@ TclCompileEnsemble( const char *word; Tcl_IncrRefCount(replaced); - - /* - * This is where we return to if we are parsing multiple nested compiled - * ensembles. [info object] is such a beast. - */ - - checkNextWord: if (parsePtr->numWords < depth + 1) { goto failed; } @@ -2769,6 +2762,12 @@ TclCompileEnsemble( goto failed; } + /* + * This is where we return to if we are parsing multiple nested compiled + * ensembles. [info object] is such a beast. + */ + + checkNextWord: word = tokenPtr[1].start; numBytes = tokenPtr[1].size; @@ -2979,6 +2978,17 @@ TclCompileEnsemble( if (cmdPtr->compileProc == TclCompileEnsemble) { tokenPtr = TokenAfter(tokenPtr); + if (parsePtr->numWords < depth + 1 + || tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + /* + * Too hard because the user has done something unpleasant like + * omitting the sub-ensemble's command name or used a non-constant + * name for a sub-ensemble's command name; we respond by bailing + * out completely (this is a rare case). [Bug 6d2f249a01] + */ + + goto cleanup; + } ensemble = (Tcl_Command) cmdPtr; goto checkNextWord; } diff --git a/tests/namespace.test b/tests/namespace.test index fab0040..cded1f4 100644 --- a/tests/namespace.test +++ b/tests/namespace.test @@ -2949,6 +2949,10 @@ test namespace-54.1 {leak on namespace deletion} -constraints {memory} \ rename getbytes {} unset i ns start end } -result 0 + +test namespace-55.1 {compiled ensembles inside compiled ensembles: Bug 6d2f249a01} { + info class [format %s constructor] oo::object +} "" # cleanup catch {rename cmd1 {}} -- cgit v0.12 From ec5c5c277f107a8e3cd0e40160019687d18f001b Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 12 May 2014 13:09:00 +0000 Subject: Possible fix for [47d66253c92197d30bff280b02e0a9e62f07cee2|47d66253c9]: "lsearch -sorted -integer" on 64bit system --- generic/tclCmdIL.c | 29 +++++++++++++++-------------- generic/tclExecute.c | 24 ------------------------ generic/tclInt.h | 24 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/generic/tclCmdIL.c b/generic/tclCmdIL.c index 41c1eb6..db216e5 100644 --- a/generic/tclCmdIL.c +++ b/generic/tclCmdIL.c @@ -29,7 +29,7 @@ typedef struct SortElement { union { /* The value that we sorting by. */ const char *strValuePtr; - long intValue; + Tcl_WideInt wideValue; double doubleValue; Tcl_Obj *objValuePtr; } collationKey; @@ -2893,7 +2893,8 @@ Tcl_LsearchObjCmd( { const char *bytes, *patternBytes; int i, match, index, result, listc, length, elemLen, bisect; - int dataType, isIncreasing, lower, upper, patInt, objInt, offset; + int dataType, isIncreasing, lower, upper, offset; + Tcl_WideInt patWide, objWide; int allMatches, inlineReturn, negatedMatch, returnSubindices, noCase; double patDouble, objDouble; SortInfo sortInfo; @@ -3212,7 +3213,7 @@ Tcl_LsearchObjCmd( patternBytes = TclGetStringFromObj(patObj, &length); break; case INTEGER: - result = TclGetIntFromObj(interp, patObj, &patInt); + result = TclGetWideIntFromObj(interp, patObj, &patWide); if (result != TCL_OK) { goto done; } @@ -3281,13 +3282,13 @@ Tcl_LsearchObjCmd( match = DictionaryCompare(patternBytes, bytes); break; case INTEGER: - result = TclGetIntFromObj(interp, itemPtr, &objInt); + result = TclGetWideIntFromObj(interp, itemPtr, &objWide); if (result != TCL_OK) { goto done; } - if (patInt == objInt) { + if (patWide == objWide) { match = 0; - } else if (patInt < objInt) { + } else if (patWide < objWide) { match = -1; } else { match = 1; @@ -3400,14 +3401,14 @@ Tcl_LsearchObjCmd( break; case INTEGER: - result = TclGetIntFromObj(interp, itemPtr, &objInt); + result = TclGetWideIntFromObj(interp, itemPtr, &objWide); if (result != TCL_OK) { if (listPtr != NULL) { Tcl_DecrRefCount(listPtr); } goto done; } - match = (objInt == patInt); + match = (objWide == patWide); break; case REAL: @@ -3971,13 +3972,13 @@ Tcl_LsortObjCmd( if (sortMode == SORTMODE_ASCII) { elementArray[i].collationKey.strValuePtr = TclGetString(indexPtr); } else if (sortMode == SORTMODE_INTEGER) { - long a; + Tcl_WideInt a; - if (TclGetLongFromObj(sortInfo.interp, indexPtr, &a) != TCL_OK) { + if (TclGetWideIntFromObj(sortInfo.interp, indexPtr, &a) != TCL_OK) { sortInfo.resultCode = TCL_ERROR; goto done1; } - elementArray[i].collationKey.intValue = a; + elementArray[i].collationKey.wideValue = a; } else if (sortMode == SORTMODE_REAL) { double a; @@ -4226,10 +4227,10 @@ SortCompare( order = DictionaryCompare(elemPtr1->collationKey.strValuePtr, elemPtr2->collationKey.strValuePtr); } else if (infoPtr->sortMode == SORTMODE_INTEGER) { - long a, b; + Tcl_WideInt a, b; - a = elemPtr1->collationKey.intValue; - b = elemPtr2->collationKey.intValue; + a = elemPtr1->collationKey.wideValue; + b = elemPtr2->collationKey.wideValue; order = ((a >= b) - (a <= b)); } else if (infoPtr->sortMode == SORTMODE_REAL) { double a, b; diff --git a/generic/tclExecute.c b/generic/tclExecute.c index 4ecca5b..d8c5935 100644 --- a/generic/tclExecute.c +++ b/generic/tclExecute.c @@ -556,30 +556,6 @@ VarHashCreateVar( : Tcl_GetBooleanFromObj((interp), (objPtr), (boolPtr))) /* - * Macro used in this file to save a function call for common uses of - * Tcl_GetWideIntFromObj(). The ANSI C "prototype" is: - * - * MODULE_SCOPE int TclGetWideIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, - * Tcl_WideInt *wideIntPtr); - */ - -#ifdef TCL_WIDE_INT_IS_LONG -#define TclGetWideIntFromObj(interp, objPtr, wideIntPtr) \ - (((objPtr)->typePtr == &tclIntType) \ - ? (*(wideIntPtr) = (Tcl_WideInt) \ - ((objPtr)->internalRep.longValue), TCL_OK) : \ - Tcl_GetWideIntFromObj((interp), (objPtr), (wideIntPtr))) -#else /* !TCL_WIDE_INT_IS_LONG */ -#define TclGetWideIntFromObj(interp, objPtr, wideIntPtr) \ - (((objPtr)->typePtr == &tclWideIntType) \ - ? (*(wideIntPtr) = (objPtr)->internalRep.wideValue, TCL_OK) : \ - ((objPtr)->typePtr == &tclIntType) \ - ? (*(wideIntPtr) = (Tcl_WideInt) \ - ((objPtr)->internalRep.longValue), TCL_OK) : \ - Tcl_GetWideIntFromObj((interp), (objPtr), (wideIntPtr))) -#endif /* TCL_WIDE_INT_IS_LONG */ - -/* * Macro used to make the check for type overflow more mnemonic. This works by * comparing sign bits; the rest of the word is irrelevant. The ANSI C * "prototype" (where inttype_t is any integer type) is: diff --git a/generic/tclInt.h b/generic/tclInt.h index d775a4a..b1a368e 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2453,6 +2453,30 @@ typedef struct List { #endif /* + * Macro used to save a function call for common uses of + * Tcl_GetWideIntFromObj(). The ANSI C "prototype" is: + * + * MODULE_SCOPE int TclGetWideIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + * Tcl_WideInt *wideIntPtr); + */ + +#ifdef TCL_WIDE_INT_IS_LONG +#define TclGetWideIntFromObj(interp, objPtr, wideIntPtr) \ + (((objPtr)->typePtr == &tclIntType) \ + ? (*(wideIntPtr) = (Tcl_WideInt) \ + ((objPtr)->internalRep.longValue), TCL_OK) : \ + Tcl_GetWideIntFromObj((interp), (objPtr), (wideIntPtr))) +#else /* !TCL_WIDE_INT_IS_LONG */ +#define TclGetWideIntFromObj(interp, objPtr, wideIntPtr) \ + (((objPtr)->typePtr == &tclWideIntType) \ + ? (*(wideIntPtr) = (objPtr)->internalRep.wideValue, TCL_OK) : \ + ((objPtr)->typePtr == &tclIntType) \ + ? (*(wideIntPtr) = (Tcl_WideInt) \ + ((objPtr)->internalRep.longValue), TCL_OK) : \ + Tcl_GetWideIntFromObj((interp), (objPtr), (wideIntPtr))) +#endif /* TCL_WIDE_INT_IS_LONG */ + +/* * Flag values for TclTraceDictPath(). * * DICT_PATH_READ indicates that all entries on the path must exist but no -- cgit v0.12 From 19b4d8d7fb173813c59f7281e52922921ef9f3cb Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 12 May 2014 17:19:16 +0000 Subject: Have the [chan push] machinery ReadRaw() directly into the argument to be passed to the read method of the channel transformation command. Save a copy. --- generic/tclIORTrans.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/generic/tclIORTrans.c b/generic/tclIORTrans.c index 1dff4b3..b9dd1d6 100644 --- a/generic/tclIORTrans.c +++ b/generic/tclIORTrans.c @@ -457,8 +457,7 @@ static void TimerKill(ReflectedTransform *rtPtr); static void TimerSetup(ReflectedTransform *rtPtr); static void TimerRun(ClientData clientData); static int TransformRead(ReflectedTransform *rtPtr, - int *errorCodePtr, unsigned char *buf, - int toRead); + int *errorCodePtr, Tcl_Obj *bufObj); static int TransformWrite(ReflectedTransform *rtPtr, int *errorCodePtr, unsigned char *buf, int toWrite); @@ -1063,6 +1062,7 @@ ReflectInput( { ReflectedTransform *rtPtr = clientData; int gotBytes, copied, readBytes; + Tcl_Obj *bufObj; /* * The following check can be done before thread redirection, because we @@ -1078,6 +1078,9 @@ ReflectInput( Tcl_Preserve(rtPtr); + /* TODO: Consider a more appropriate buffer size. */ + bufObj = Tcl_NewByteArrayObj(NULL, toRead); + Tcl_IncrRefCount(bufObj); gotBytes = 0; while (toRead > 0) { /* @@ -1129,7 +1132,9 @@ ReflectInput( goto stop; } - readBytes = Tcl_ReadRaw(rtPtr->parent, buf, toRead); + + readBytes = Tcl_ReadRaw(rtPtr->parent, + (char *) Tcl_SetByteArrayLength(bufObj, toRead), toRead); if (readBytes < 0) { /* * Report errors to caller. The state of the seek system is @@ -1213,12 +1218,20 @@ ReflectInput( * iteration will put it into the result. */ - if (!TransformRead(rtPtr, errorCodePtr, UCHARP(buf), readBytes)) { + Tcl_SetByteArrayLength(bufObj, readBytes); + if (!TransformRead(rtPtr, errorCodePtr, bufObj)) { goto error; } + if (Tcl_IsShared(bufObj)) { + Tcl_DecrRefCount(bufObj); + bufObj = Tcl_NewObj(); + Tcl_IncrRefCount(bufObj); + } + Tcl_SetByteArrayLength(bufObj, 0); } /* while toRead > 0 */ stop: + Tcl_DecrRefCount(bufObj); Tcl_Release(rtPtr); return gotBytes; @@ -3067,10 +3080,8 @@ static int TransformRead( ReflectedTransform *rtPtr, int *errorCodePtr, - unsigned char *buf, - int toRead) + Tcl_Obj *bufObj) { - Tcl_Obj *bufObj; Tcl_Obj *resObj; int bytec; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ @@ -3083,8 +3094,8 @@ TransformRead( if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; - p.transform.buf = (char *) buf; - p.transform.size = toRead; + p.transform.buf = (char *) Tcl_GetByteArrayFromObj(bufObj, + &(p.transform.size)); ForwardOpToOwnerThread(rtPtr, ForwardedInput, &p); @@ -3104,12 +3115,8 @@ TransformRead( /* ASSERT: rtPtr->method & FLAG(METH_READ) */ /* ASSERT: rtPtr->mode & TCL_READABLE */ - bufObj = Tcl_NewByteArrayObj((unsigned char *) buf, toRead); - Tcl_IncrRefCount(bufObj); - if (InvokeTclMethod(rtPtr, "read", bufObj, NULL, &resObj) != TCL_OK) { Tcl_SetChannelError(rtPtr->chan, resObj); - Tcl_DecrRefCount(bufObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ *errorCodePtr = EINVAL; return 0; @@ -3118,7 +3125,6 @@ TransformRead( bytev = Tcl_GetByteArrayFromObj(resObj, &bytec); ResultAdd(&rtPtr->result, bytev, bytec); - Tcl_DecrRefCount(bufObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ return 1; } -- cgit v0.12 From 06285d2a11026ced76b58653449e181fd48ac13c Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 12 May 2014 21:50:55 +0000 Subject: Restore the largefileSupport constraint on Darwin, where tests (chan)io-34.21 take an unbearable 90 seconds each to complete. --- tests/chanio.test | 2 +- tests/io.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/chanio.test b/tests/chanio.test index 5ea7ec1..2f2540e 100644 --- a/tests/chanio.test +++ b/tests/chanio.test @@ -41,7 +41,7 @@ namespace eval ::tcl::test::io { # You need a *very* special environment to do some tests. In # particular, many file systems do not support large-files... - testConstraint largefileSupport 1 + testConstraint largefileSupport [expr {$::tcl_platform(os) ne "Darwin"}] # some tests can only be run is umask is 2 # if "umask" cannot be run, the tests will be skipped. diff --git a/tests/io.test b/tests/io.test index 7f1a357..f692e43 100644 --- a/tests/io.test +++ b/tests/io.test @@ -41,7 +41,7 @@ testConstraint testthread [llength [info commands testthread]] # You need a *very* special environment to do some tests. In # particular, many file systems do not support large-files... -testConstraint largefileSupport 1 +testConstraint largefileSupport [expr {$::tcl_platform(os) ne "Darwin"}] # some tests can only be run is umask is 2 # if "umask" cannot be run, the tests will be skipped. -- cgit v0.12 From b482771754f287160a211cfe25fb24e135b52101 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Tue, 13 May 2014 11:23:40 +0000 Subject: [958bc05fbe]: Clarify "clock format" using "%R" --- doc/clock.n | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/clock.n b/doc/clock.n index 7c4c3df..a0cc63e 100644 --- a/doc/clock.n +++ b/doc/clock.n @@ -626,8 +626,9 @@ On output, produces a locale-dependent time of day representation on a 12-hour clock. On input, accepts whatever \fB%r\fR produces. .TP \fB%R\fR -On output, produces a locale-dependent time of day representation on a -24-hour clock. On input, accepts whatever \fB%R\fR produces. +On output, the time in 24-hour notation (%H:%M). For a version +including the seconds, see \fB%T\fR below. On input, accepts whatever +\fB%R\fR produces. .TP \fB%s\fR On output, simply formats the \fItimeVal\fR argument as a decimal -- cgit v0.12 From c3df58587ce6e9f21b652b23eb7f56f852a326f7 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 13 May 2014 18:24:23 +0000 Subject: Rework Tcl_ReadRaw() mostly taking things out of the loop that never repeat. --- generic/tclIO.c | 56 ++++++++++++++++++++------------------------------------ 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 41f555b..a82c36b 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4985,7 +4985,7 @@ Tcl_ReadRaw( Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ - int nread, copied, copiedNow; + int nread, copied, copiedNow = INT_MAX; /* * The check below does too much because it will reject a call to this @@ -5010,44 +5010,28 @@ Tcl_ReadRaw( */ Tcl_Preserve(chanPtr); - for (copied = 0; copied < bytesToRead; copied += copiedNow) { - copiedNow = CopyBuffer(chanPtr, bufPtr + copied, - bytesToRead - copied); - if (copiedNow == 0) { - if (GotFlag(statePtr, CHANNEL_EOF)) { - break; - } - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - break; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); - } - - /* - * Now go to the driver to get as much as is possible to - * fill the remaining request. Do all the error handling by - * ourselves. The code was stolen from 'GetInput' and - * slightly adapted (different return value here). - * - * The case of 'bytesToRead == 0' at this point cannot - * happen. - */ + for (copied = 0; bytesToRead > 0 && copiedNow > 0; + bufPtr+=copiedNow, bytesToRead-=copiedNow, copied+=copiedNow) { + copiedNow = CopyBuffer(chanPtr, bufPtr, bytesToRead); + } - nread = ChanRead(chanPtr, bufPtr + copied, - bytesToRead - copied); + if (bytesToRead > 0) { + /* + * Now go to the driver to get as much as is possible to + * fill the remaining request. Since we're directly filling + * the caller's buffer, retain the blocked flag. + */ - if (nread < 0) { - if (GotFlag(statePtr, CHANNEL_BLOCKED) && copied > 0) { -/* TODO: comment out? */ -// ResetFlag(statePtr, CHANNEL_BLOCKED); - } else { - copied = -1; - } - } else { - copied += nread; + nread = ChanRead(chanPtr, bufPtr, bytesToRead); + if (nread < 0) { + if (!GotFlag(statePtr, CHANNEL_BLOCKED) || copied == 0) { + copied = -1; } - break; + } else { + copied += nread; + } + if (copied != 0) { + ResetFlag(statePtr, CHANNEL_EOF); } } -- cgit v0.12 From 5e610145644e54a301c3f508b6c6fb4dcf95f7b6 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Wed, 14 May 2014 09:11:42 +0000 Subject: Fix 3 test-cases which started failing on Windows --- tests/io.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/io.test b/tests/io.test index f692e43..b99c4e9 100644 --- a/tests/io.test +++ b/tests/io.test @@ -6825,6 +6825,7 @@ test io-52.12 {coverage of -translation auto} { set in [open $path(test1)] chan configure $in -buffersize 8 set out [open $path(test2) w] + chan configure $out -translation lf fcopy $in $out close $in close $out @@ -6839,6 +6840,7 @@ test io-52.13 {coverage of -translation cr} { set in [open $path(test1)] chan configure $in -buffersize 8 -translation cr set out [open $path(test2) w] + chan configure $out -translation lf fcopy $in $out close $in close $out @@ -6853,6 +6855,7 @@ test io-52.14 {coverage of -translation crlf} { set in [open $path(test1)] chan configure $in -buffersize 8 -translation crlf set out [open $path(test2) w] + chan configure $out -translation lf fcopy $in $out close $in close $out -- cgit v0.12 From 40646ad79cd332a232c9d5afea6f879222e9ccb9 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 15 May 2014 11:04:35 +0000 Subject: Push the setting and clearing of CHANNEL_BLOCKED flag to the more inner parts of the channel read machinery. --- generic/tclIO.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index a82c36b..3416b64 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -383,6 +383,11 @@ ChanRead( */ assert(dstSize > 0); + /* + * Each read op must set the blocked and eof states anew, not let + * the effect of prior reads leak through. + */ + ResetFlag(chanPtr->state, CHANNEL_BLOCKED | CHANNEL_EOF); if (WillRead(chanPtr) < 0) { return -1; } @@ -3944,6 +3949,9 @@ Tcl_GetsObj( goto done; } + /* TODO: Locate better place(s) to reset this flag */ + ResetFlag(statePtr, CHANNEL_BLOCKED); + /* * A binary version of Tcl_GetsObj. This could also handle encodings that * are ascii-7 pure (iso8859, utf-8, ...) with a final encoding conversion @@ -4377,7 +4385,6 @@ TclGetsObjBinary( if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { goto restore; } - ResetFlag(statePtr, CHANNEL_BLOCKED); } if (GetInput(chanPtr) != 0) { goto restore; @@ -4643,13 +4650,13 @@ FilterInputBytes( */ read: + /* TODO: Move this check to the goto */ if (GotFlag(statePtr, CHANNEL_BLOCKED)) { if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { gsPtr->charsWrote = 0; gsPtr->rawRead = 0; return -1; } - ResetFlag(statePtr, CHANNEL_BLOCKED); } if (GetInput(chanPtr) != 0) { gsPtr->charsWrote = 0; @@ -5168,6 +5175,8 @@ DoReadChars( } } + /* Must clear the BLOCKED flag here since we check before reading */ + ResetFlag(statePtr, CHANNEL_BLOCKED); for (copied = 0; (unsigned) toRead > 0; ) { copiedNow = -1; if (statePtr->inQueueHead != NULL) { @@ -5202,7 +5211,6 @@ DoReadChars( if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { break; } - ResetFlag(statePtr, CHANNEL_BLOCKED); } result = GetInput(chanPtr); if (chanPtr != statePtr->topChanPtr) { @@ -6619,12 +6627,7 @@ CheckChannelErrors( } if (direction == TCL_READABLE) { - /* - * Clear the BLOCKED bit. We want to discover this condition - * anew in each operation. - */ - - ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_NEED_MORE_DATA); + ResetFlag(statePtr, CHANNEL_NEED_MORE_DATA); } return 0; @@ -8801,9 +8804,7 @@ DoRead( while (bufPtr == NULL || !IsBufferFull(bufPtr)) { int code; - ResetFlag(statePtr, CHANNEL_BLOCKED); moreData: - code = GetInput(chanPtr); bufPtr = statePtr->inQueueHead; -- cgit v0.12 From 47b1f4425678fcc051e9a75f59f6c4cd4f21b176 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Thu, 15 May 2014 14:54:45 +0000 Subject: Fix [3118489]: NUL in filenames. (On Windows, protect against invalid use of ':' in filenames as well) --- tests/cmdAH.test | 3 +++ unix/tclUnixFile.c | 6 ++++++ win/tclWinFile.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/tests/cmdAH.test b/tests/cmdAH.test index dc61ac6..4ca90c6 100644 --- a/tests/cmdAH.test +++ b/tests/cmdAH.test @@ -104,6 +104,9 @@ test cmdAH-2.6.1 {Tcl_CdObjCmd} { list [catch {cd ""} msg] $msg } {1 {couldn't change working directory to "": no such file or directory}} +test cmdAH-2.6.3 {Tcl_CdObjCmd, bug #3118489} -returnCodes error -body { + cd .\0 +} -result "couldn't change working directory to \".\0\": no such file or directory" test cmdAH-2.7 {Tcl_ConcatObjCmd} { concat } {} diff --git a/unix/tclUnixFile.c b/unix/tclUnixFile.c index 29f1aba..c5f75a7 100644 --- a/unix/tclUnixFile.c +++ b/unix/tclUnixFile.c @@ -1111,6 +1111,12 @@ TclNativeCreateNativeRep( str = Tcl_GetStringFromObj(validPathPtr, &len); Tcl_UtfToExternalDString(NULL, str, len, &ds); len = Tcl_DStringLength(&ds) + sizeof(char); + if (strlen(Tcl_DStringValue(&ds)) < len - sizeof(char)) { + /* See bug [3118489]: NUL in filenames */ + Tcl_DecrRefCount(validPathPtr); + Tcl_DStringFree(&ds); + return NULL; + } Tcl_DecrRefCount(validPathPtr); nativePathPtr = ckalloc((unsigned) len); memcpy((void*)nativePathPtr, (void*)Tcl_DStringValue(&ds), (size_t) len); diff --git a/win/tclWinFile.c b/win/tclWinFile.c index ed0c40f..9bf63b1 100644 --- a/win/tclWinFile.c +++ b/win/tclWinFile.c @@ -1856,6 +1856,9 @@ TclpObjChdir( nativePath = (const TCHAR *) Tcl_FSGetNativePath(pathPtr); + if (!nativePath) { + return -1; + } result = (*tclWinProcs->setCurrentDirectoryProc)(nativePath); if (result == 0) { @@ -3200,13 +3203,69 @@ TclNativeCreateNativeRep( Tcl_WinUtfToTChar(str, len, &ds); if (tclWinProcs->useWide) { WCHAR *wp = (WCHAR *) Tcl_DStringValue(&ds); - for (; *wp; ++wp) { - if (*wp=='/') { + len = Tcl_DStringLength(&ds)>>1; + /* + ** If path starts with "//?/" or "\\?\" (extended path), translate + ** any slashes to backslashes but accept the '?' as being valid. + */ + if ((str[0]=='\\' || str[0]=='/') && (str[1]=='\\' || str[1]=='/') + && str[2]=='?' && (str[3]=='\\' || str[3]=='/')) { + wp[0] = wp[1] = wp[3] = '\\'; + str += 4; + wp += 4; + len -= 4; + } + /* + ** If there is a drive prefix, the ':' must be considered valid. + **/ + if (((str[0]>='A'&&str[0]<='Z') || (str[0]>='a'&&str[0]<='z')) + && str[1]==':') { + wp += 2; + len -= 2; + } + while (len-->0) { + if ((*wp < ' ') || wcschr(L"\"*:<>?|", *wp)) { + Tcl_DecrRefCount(validPathPtr); + Tcl_DStringFree(&ds); + return NULL; + } else if (*wp=='/') { *wp = '\\'; } + ++wp; } len = Tcl_DStringLength(&ds) + sizeof(WCHAR); } else { + char *p = Tcl_DStringValue(&ds); + len = Tcl_DStringLength(&ds); + /* + ** If path starts with "//?/" or "\\?\" (extended path), translate + ** any slashes to backslashes but accept the '?' as being valid. + */ + if ((str[0]=='\\' || str[0]=='/') && (str[1]=='\\' || str[1]=='/') + && str[2]=='?' && (str[3]=='\\' || str[3]=='/')) { + p[0] = p[1] = p[3] = '\\'; + str += 4; + p += 4; + len -= 4; + } + /* + ** If there is a drive prefix, the ':' must be considered valid. + **/ + if (((str[0]>='A'&&str[0]<='Z') || (str[0]>='a'&&str[0]<='z')) + && str[1]==':') { + p += 2; + len -= 2; + } + while (len-->0) { + if ((*p < ' ') || strchr("\"*:<>?|", *p)) { + Tcl_DecrRefCount(validPathPtr); + Tcl_DStringFree(&ds); + return NULL; + } else if (*p=='/') { + *p = '\\'; + } + ++p; + } len = Tcl_DStringLength(&ds) + sizeof(char); } Tcl_DecrRefCount(validPathPtr); -- cgit v0.12 From 267fb4eebd7345f715153cea17de47c2396d31f8 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 15 May 2014 16:48:43 +0000 Subject: Portable test to demo bug otherwise seen only on Windows. --- tests/io.test | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/io.test b/tests/io.test index b99c4e9..b82f403 100644 --- a/tests/io.test +++ b/tests/io.test @@ -3960,6 +3960,26 @@ test io-32.11 {Tcl_Read from a pipe} {stdio openpipe} { } {{hello } {hello }} +test io-32.11.1 {Tcl_Read from a pipe} {stdio openpipe} { + file delete $path(pipe) + set f1 [open $path(pipe) w] + puts $f1 {chan configure stdout -translation crlf} + puts $f1 {puts [gets stdin]} + puts $f1 {puts [gets stdin]} + close $f1 + set f1 [open "|[list [interpreter] $path(pipe)]" r+] + puts $f1 hello + flush $f1 + set x "" + lappend x [read $f1 6] + puts $f1 hello + flush $f1 + lappend x [read $f1] + close $f1 + set x +} {{hello +} {hello +}} test io-32.12 {Tcl_Read, -nonewline} { file delete $path(test1) set f1 [open $path(test1) w] -- cgit v0.12 From 564d1813daa4ebd962bcafa76e4ce48659d16a26 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 15 May 2014 18:38:22 +0000 Subject: Branch to demo bug introduced in the parent commit. --- tests/io.test | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/io.test b/tests/io.test index c325809..b28566b 100644 --- a/tests/io.test +++ b/tests/io.test @@ -3960,6 +3960,26 @@ test io-32.11 {Tcl_Read from a pipe} {stdio openpipe} { } {{hello } {hello }} +test io-32.11.1 {Tcl_Read from a pipe} {stdio openpipe} { + file delete $path(pipe) + set f1 [open $path(pipe) w] + puts $f1 {chan configure stdout -translation crlf} + puts $f1 {puts [gets stdin]} + puts $f1 {puts [gets stdin]} + close $f1 + set f1 [open "|[list [interpreter] $path(pipe)]" r+] + puts $f1 hello + flush $f1 + set x "" + lappend x [read $f1 6] + puts $f1 hello + flush $f1 + lappend x [read $f1] + close $f1 + set x +} {{hello +} {hello +}} test io-32.12 {Tcl_Read, -nonewline} { file delete $path(test1) set f1 [open $path(test1) w] -- cgit v0.12 From 8019dad5cd3e9bb29762e4d700f067eed9873084 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 16 May 2014 12:56:01 +0000 Subject: More tests to demo the bug more directly. --- tests/io.test | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/io.test b/tests/io.test index b28566b..53b7105 100644 --- a/tests/io.test +++ b/tests/io.test @@ -1559,6 +1559,45 @@ test io-13.8 {TranslateInputEOL: auto mode: \r\n} { close $f set x } "abcd\ndef" +test io-13.8.1 {TranslateInputEOL: auto mode: \r\n} { + set f [open $path(test1) w] + fconfigure $f -translation lf + puts -nonewline $f "abcd\r\ndef" + close $f + set f [open $path(test1)] + fconfigure $f -translation auto + set x {} + lappend x [read $f 5] + lappend x [read $f] + close $f + set x +} [list "abcd\n" "def"] +test io-13.8.2 {TranslateInputEOL: auto mode: \r\n} { + set f [open $path(test1) w] + fconfigure $f -translation lf + puts -nonewline $f "abcd\r\ndef" + close $f + set f [open $path(test1)] + fconfigure $f -translation auto -buffersize 6 + set x {} + lappend x [read $f 5] + lappend x [read $f] + close $f + set x +} [list "abcd\n" "def"] +test io-13.8.3 {TranslateInputEOL: auto mode: \r\n} { + set f [open $path(test1) w] + fconfigure $f -translation lf + puts -nonewline $f "abcd\r\n\r\ndef" + close $f + set f [open $path(test1)] + fconfigure $f -translation auto -buffersize 7 + set x {} + lappend x [read $f 5] + lappend x [read $f] + close $f + set x +} [list "abcd\n" "\ndef"] test io-13.9 {TranslateInputEOL: auto mode: \r followed by not \n} { set f [open $path(test1) w] fconfigure $f -translation lf -- cgit v0.12 From 4830ed53a3b546ff699230e332c0a6d4fecf5a24 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 16 May 2014 14:40:39 +0000 Subject: Bug fix - accept consumption of the trailing newline in crlf with no characters produced. Also delete false assertions. --- generic/tclIO.c | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 139a05e..16fc685 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5400,7 +5400,6 @@ ReadChars( * record \r or \n yet. */ - assert(dstRead + 1 == dstDecoded); assert(dst[dstRead] == '\r'); assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); @@ -5421,7 +5420,6 @@ ReadChars( assert(dstWrote == 0); assert(dstRead == 0); - assert(dstDecoded == 1); /* * We decoded only the bare cr, and we cannot read a @@ -5476,6 +5474,13 @@ ReadChars( return 1; } + /* + * Revise the dstRead value so that the numChars calc + * below correctly computes zero characters read. + */ + + dstRead = numChars; + /* FALL THROUGH - get more data (dstWrote == 0) */ } @@ -5502,16 +5507,38 @@ ReadChars( } if (dstWrote == 0) { + ChannelBuffer *nextPtr; - /* - * We were not able to read any chars. Maybe there were - * not enough src bytes to decode into a char. Maybe - * a lone \r could not be translated (crlf mode). Need - * to combine any unused src bytes we have in the first - * buffer with subsequent bytes to try again. + /* We were not able to read any chars. */ + + assert (numChars == 0); + + /* + * There is one situation where this is the correct final + * result. If the src buffer contains only a single \n + * byte, and we are in TCL_TRANSLATE_AUTO mode, and + * when the translation pass was made the INPUT_SAW_CR + * flag was set on the channel. In that case, the + * correct behavior is to consume that \n and produce the + * empty string. + */ + + if (dst[0] == '\n') { + assert(statePtr->inputTranslation == TCL_TRANSLATE_AUTO); + assert(dstRead == 1); + + goto consume; + } + + /* Otherwise, reading zero characters indicates there's + * something incomplete at the end of the src buffer. + * Maybe there were not enough src bytes to decode into + * a char. Maybe a lone \r could not be translated (crlf + * mode). Need to combine any unused src bytes we have + * in the first buffer with subsequent bytes to try again. */ - ChannelBuffer *nextPtr = bufPtr->nextPtr; + nextPtr = bufPtr->nextPtr; if (nextPtr == NULL) { if (srcLen > 0) { @@ -5548,6 +5575,7 @@ ReadChars( statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; + consume: bufPtr->nextRemoved += srcRead; if (dstWrote > srcRead + 1) { *factorPtr = dstWrote * UTF_EXPANSION_FACTOR / srcRead; -- cgit v0.12 From 46b60b08860ef3c85c1dc0a9250fd1c499714a6a Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 16 May 2014 18:45:37 +0000 Subject: Move the resets and testings of the BLOCKED flag to where they make more sense. --- generic/tclIO.c | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index a5c77e8..a128d7c 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -3949,9 +3949,6 @@ Tcl_GetsObj( goto done; } - /* TODO: Locate better place(s) to reset this flag */ - ResetFlag(statePtr, CHANNEL_BLOCKED); - /* * A binary version of Tcl_GetsObj. This could also handle encodings that * are ascii-7 pure (iso8859, utf-8, ...) with a final encoding conversion @@ -4182,6 +4179,7 @@ Tcl_GetsObj( Tcl_SetObjLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; + ResetFlag(statePtr, CHANNEL_BLOCKED); goto done; } goto gotEOL; @@ -4381,10 +4379,9 @@ TclGetsObjBinary( * device. Side effect is to allocate another channel buffer. */ - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - goto restore; - } + if (GotFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_NONBLOCKING) + == (CHANNEL_BLOCKED|CHANNEL_NONBLOCKING)) { + goto restore; } if (GetInput(chanPtr) != 0) { goto restore; @@ -4447,6 +4444,7 @@ TclGetsObjBinary( byteArray = Tcl_SetByteArrayLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; + ResetFlag(statePtr, CHANNEL_BLOCKED); goto done; } goto gotEOL; @@ -4650,14 +4648,6 @@ FilterInputBytes( */ read: - /* TODO: Move this check to the goto */ - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - gsPtr->charsWrote = 0; - gsPtr->rawRead = 0; - return -1; - } - } if (GetInput(chanPtr) != 0) { gsPtr->charsWrote = 0; gsPtr->rawRead = 0; @@ -4745,9 +4735,15 @@ FilterInputBytes( } else { /* * There are no more cached raw bytes left. See if we can get - * some more. + * some more, but avoid blocking on a non-blocking channel. */ + if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) + == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { + gsPtr->charsWrote = 0; + gsPtr->rawRead = 0; + return -1; + } goto read; } } else { @@ -5207,10 +5203,9 @@ DoReadChars( if (GotFlag(statePtr, CHANNEL_EOF)) { break; } - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - break; - } + if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) + == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { + break; } result = GetInput(chanPtr); if (chanPtr != statePtr->topChanPtr) { @@ -8935,8 +8930,8 @@ DoRead( RecycleBuffer(statePtr, bufPtr, 0); } - if (GotFlag(statePtr, CHANNEL_NONBLOCKING) - && GotFlag(statePtr, CHANNEL_BLOCKED)) { + if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) + == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { break; } } -- cgit v0.12 From 8899c7cda9cd674018d4cc5a807cdaa5076f0f02 Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 16 May 2014 19:11:49 +0000 Subject: Improved use of EOF state to avoid worthless allocations. --- generic/tclIO.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index a128d7c..09fa55e 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -6079,6 +6079,18 @@ GetInput( return EINVAL; } + /* + * For a channel at EOF do not bother allocating buffers; there's + * nothing more to read. Avoid calling the driver inputproc in + * case some of them do not react well to additional calls after + * they've reported an eof state.. + * TODO: Candidate for a can't happen panic. + */ + + if (GotFlag(statePtr, CHANNEL_EOF)) { + return 0; + } + /* * First check for more buffers in the pushback area of the topmost * channel in the stack and use them. They can be the result of a @@ -6143,16 +6155,6 @@ GetInput( statePtr->inQueueTail = bufPtr; } - /* - * TODO - consider escape before buffer alloc - * If EOF is set, we should avoid calling the driver because on some - * platforms it is impossible to read from a device after EOF. - */ - - if (GotFlag(statePtr, CHANNEL_EOF)) { - return 0; - } - PreserveChannelBuffer(bufPtr); nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead); -- cgit v0.12 From e14f3688ed41e7bcae1a5448ba213d5d8d063ecf Mon Sep 17 00:00:00 2001 From: ferrieux Date: Fri, 16 May 2014 21:33:41 +0000 Subject: Let the generated Makefile be emacs-friendly by avoiding spurious empty lines and misplaced tabs. Useful e.g. to just set CFLAGS to debug and save. --- unix/Makefile.in | 2 ++ unix/configure | 3 +++ unix/configure.in | 3 +++ win/Makefile.in | 2 ++ 4 files changed, 10 insertions(+) diff --git a/unix/Makefile.in b/unix/Makefile.in index 69dd14f..f151ebb 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -2089,9 +2089,11 @@ alldist: dist html: ${NATIVE_TCLSH} $(BUILD_HTML) @EXTRA_BUILD_HTML@ + html-tcl: ${NATIVE_TCLSH} $(BUILD_HTML) --tcl @EXTRA_BUILD_HTML@ + html-tk: ${NATIVE_TCLSH} $(BUILD_HTML) --tk @EXTRA_BUILD_HTML@ diff --git a/unix/configure b/unix/configure index ce5db6a..bd85ba4 100755 --- a/unix/configure +++ b/unix/configure @@ -1338,6 +1338,9 @@ TCL_MINOR_VERSION=6 TCL_PATCH_LEVEL=".1" VERSION=${TCL_VERSION} +EXTRA_INSTALL_BINARIES=${EXTRA_INSTALL_BINARIES:-"@:"} +EXTRA_BUILD_HTML=${EXTRA_BUILD_HTML:-"@:"} + #------------------------------------------------------------------------ # Setup configure arguments for bundled packages #------------------------------------------------------------------------ diff --git a/unix/configure.in b/unix/configure.in index 61ad30f..cb6cf82 100644 --- a/unix/configure.in +++ b/unix/configure.in @@ -28,6 +28,9 @@ TCL_MINOR_VERSION=6 TCL_PATCH_LEVEL=".1" VERSION=${TCL_VERSION} +EXTRA_INSTALL_BINARIES=${EXTRA_INSTALL_BINARIES:-"@:"} +EXTRA_BUILD_HTML=${EXTRA_BUILD_HTML:-"@:"} + #------------------------------------------------------------------------ # Setup configure arguments for bundled packages #------------------------------------------------------------------------ diff --git a/win/Makefile.in b/win/Makefile.in index fd80010..67cf66a 100644 --- a/win/Makefile.in +++ b/win/Makefile.in @@ -850,8 +850,10 @@ TOOL_DIR=$(ROOT_DIR)/tools HTML_INSTALL_DIR=$(ROOT_DIR)/html html: $(MAKE) shell SCRIPT="$(TOOL_DIR)/tcltk-man2html.tcl --htmldir=$(HTML_INSTALL_DIR) --srcdir=$(ROOT_DIR)/.. $(BUILD_HTML_FLAGS)" + html-tcl: $(TCLSH) $(MAKE) shell SCRIPT="$(TOOL_DIR)/tcltk-man2html.tcl --htmldir=$(HTML_INSTALL_DIR) --srcdir=$(ROOT_DIR)/.. $(BUILD_HTML_FLAGS) --tcl" + html-tk: $(TCLSH) $(MAKE) shell SCRIPT="$(TOOL_DIR)/tcltk-man2html.tcl --htmldir=$(HTML_INSTALL_DIR) --srcdir=$(ROOT_DIR)/.. $(BUILD_HTML_FLAGS) --tk" -- cgit v0.12 From c0033298fb580b7384b19cd188887af3ca9de2ba Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 16 May 2014 23:17:15 +0000 Subject: Merge flag changes. - Wow, no trouble with [chan push] demonstrated. --- generic/tclIO.c | 76 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index fd1ce4f..8d9d3d8 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -421,6 +421,11 @@ ChanRead( */ assert(dstSize > 0); + /* + * Each read op must set the blocked and eof states anew, not let + * the effect of prior reads leak through. + */ + ResetFlag(chanPtr->state, CHANNEL_BLOCKED | CHANNEL_EOF); if (WillRead(chanPtr) < 0) { return -1; } @@ -4598,6 +4603,7 @@ Tcl_GetsObj( Tcl_SetObjLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; + ResetFlag(statePtr, CHANNEL_BLOCKED); goto done; } goto gotEOL; @@ -4801,11 +4807,9 @@ TclGetsObjBinary( * device. Side effect is to allocate another channel buffer. */ - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - goto restore; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); + if (GotFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_NONBLOCKING) + == (CHANNEL_BLOCKED|CHANNEL_NONBLOCKING)) { + goto restore; } if (GetInput(chanPtr) != 0) { goto restore; @@ -4868,6 +4872,7 @@ TclGetsObjBinary( byteArray = Tcl_SetByteArrayLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; + ResetFlag(statePtr, CHANNEL_BLOCKED); goto done; } goto gotEOL; @@ -5071,14 +5076,6 @@ FilterInputBytes( */ read: - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - gsPtr->charsWrote = 0; - gsPtr->rawRead = 0; - return -1; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); - } if (GetInput(chanPtr) != 0) { gsPtr->charsWrote = 0; gsPtr->rawRead = 0; @@ -5166,9 +5163,15 @@ FilterInputBytes( } else { /* * There are no more cached raw bytes left. See if we can get - * some more. + * some more, but avoid blocking on a non-blocking channel. */ + if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) + == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { + gsPtr->charsWrote = 0; + gsPtr->rawRead = 0; + return -1; + } goto read; } } else { @@ -5596,6 +5599,8 @@ DoReadChars( } } + /* Must clear the BLOCKED flag here since we check before reading */ + ResetFlag(statePtr, CHANNEL_BLOCKED); for (copied = 0; (unsigned) toRead > 0; ) { copiedNow = -1; if (statePtr->inQueueHead != NULL) { @@ -5625,11 +5630,9 @@ DoReadChars( if (GotFlag(statePtr, CHANNEL_EOF)) { break; } - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - break; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); + if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) + == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { + break; } result = GetInput(chanPtr); if (chanPtr != statePtr->topChanPtr) { @@ -6503,6 +6506,18 @@ GetInput( return EINVAL; } + /* + * For a channel at EOF do not bother allocating buffers; there's + * nothing more to read. Avoid calling the driver inputproc in + * case some of them do not react well to additional calls after + * they've reported an eof state.. + * TODO: Candidate for a can't happen panic. + */ + + if (GotFlag(statePtr, CHANNEL_EOF)) { + return 0; + } + /* * First check for more buffers in the pushback area of the topmost * channel in the stack and use them. They can be the result of a @@ -6567,16 +6582,6 @@ GetInput( statePtr->inQueueTail = bufPtr; } - /* - * TODO - consider escape before buffer alloc - * If EOF is set, we should avoid calling the driver because on some - * platforms it is impossible to read from a device after EOF. - */ - - if (GotFlag(statePtr, CHANNEL_EOF)) { - return 0; - } - PreserveChannelBuffer(bufPtr); nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead); @@ -7048,12 +7053,7 @@ CheckChannelErrors( } if (direction == TCL_READABLE) { - /* - * Clear the BLOCKED bit. We want to discover this condition - * anew in each operation. - */ - - ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_NEED_MORE_DATA); + ResetFlag(statePtr, CHANNEL_NEED_MORE_DATA); } return 0; @@ -9248,9 +9248,7 @@ DoRead( while (bufPtr == NULL || !IsBufferFull(bufPtr)) { int code; - ResetFlag(statePtr, CHANNEL_BLOCKED); moreData: - code = GetInput(chanPtr); bufPtr = statePtr->inQueueHead; @@ -9353,8 +9351,8 @@ DoRead( RecycleBuffer(statePtr, bufPtr, 0); } - if ((statePtr->flags & CHANNEL_NONBLOCKING || allowShortReads) - && statePtr->flags & CHANNEL_BLOCKED) { + if ((GotFlag(statePtr, CHANNEL_NONBLOCKING) || allowShortReads) + && GotFlag(statePtr, CHANNEL_BLOCKED)) { break; } } -- cgit v0.12 From b09a6f570cb33adb2f0ec8b2475573e27fab88e5 Mon Sep 17 00:00:00 2001 From: dgp Date: Sat, 17 May 2014 02:53:34 +0000 Subject: Repair broken tests iogt-2.[123]. What happened is that now that EOF flags no loger leak acros channel stack layers, an EOF in the bottom channel isn't detected in the top one until the ChanRead call at the top level actually returns 0 bytes. This causes one more query/ma --- tests/iogt.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/iogt.test b/tests/iogt.test index 0e2eb3c..5fe3dc2 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -552,6 +552,7 @@ query/maxRead read query/maxRead flush/read +query/maxRead delete/read -------- create/write @@ -604,6 +605,7 @@ read { } query/maxRead {} -1 flush/read {} {} +query/maxRead {} -1 delete/read {} *ignored* -------- create/write {} *ignored* @@ -658,6 +660,7 @@ read {%^&*()_+-= } query/maxRead {} -1 flush/read {} {} +query/maxRead {} -1 write %^&*()_+-= %^&*()_+-= write { } { -- cgit v0.12 From acbdb9cde6294dbbac2559014328039d3cf8839e Mon Sep 17 00:00:00 2001 From: dgp Date: Sat, 17 May 2014 03:05:36 +0000 Subject: Revise results of tests iogt-2.[123] to account for EOF flags no longer leaking across channel stacks. --- tests/iogt.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/iogt.test b/tests/iogt.test index dd58df0..6cc0542 100644 --- a/tests/iogt.test +++ b/tests/iogt.test @@ -480,6 +480,7 @@ query/maxRead read query/maxRead flush/read +query/maxRead delete/read -------- create/write @@ -526,6 +527,7 @@ read { } query/maxRead {} -1 flush/read {} {} +query/maxRead {} -1 delete/read {} *ignored* -------- create/write {} *ignored* @@ -577,6 +579,7 @@ write %^&*()_+-= %^&*()_+-= write { } { } +query/maxRead {} -1 delete/read {} *ignored* flush/write {} {} delete/write {} *ignored*} -- cgit v0.12 From f6becd63de09f3ad007bb2e1543cd21230242479 Mon Sep 17 00:00:00 2001 From: dgp Date: Sat, 17 May 2014 03:32:27 +0000 Subject: Simplify the inputProc of [testchannel transform]. --- generic/tclIOGT.c | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/generic/tclIOGT.c b/generic/tclIOGT.c index c5372b1..fe0a880 100644 --- a/generic/tclIOGT.c +++ b/generic/tclIOGT.c @@ -683,38 +683,35 @@ TransformInputProc( read = Tcl_ReadRaw(downChan, buf, toRead); if (read < 0) { - /* - * Report errors to caller. EAGAIN is a special situation. If we - * had some data before we report that instead of the request to - * re-try. - */ - if ((Tcl_GetErrno() == EAGAIN) && (gotBytes > 0)) { + if (Tcl_InputBlocked(downChan) && (gotBytes > 0)) { + /* + * Zero bytes available from downChan because blocked. + * But nonzero bytes already copied, so total is a + * valid blocked short read. Return to caller. + */ + break; } + /* + * Either downChan is not blocked (there's a real error). + * or it is and there are no bytes copied yet. In either + * case we want to pass the "error" along to the caller, + * either to report an error, or to signal to the caller + * that zero bytes are available because blocked. + */ + *errorCodePtr = Tcl_GetErrno(); gotBytes = -1; break; } else if (read == 0) { + /* - * Check wether we hit on EOF in the underlying channel or not. If - * not differentiate between blocking and non-blocking modes. In - * non-blocking mode we ran temporarily out of data. Signal this - * to the caller via EWOULDBLOCK and error return (-1). In the - * other cases we simply return what we got and let the caller - * wait for more. On the other hand, if we got an EOF we have to - * convert and flush all waiting partial data. + * Zero returned from Tcl_ReadRaw() always indicates EOF + * on the down channel. */ - if (!Tcl_Eof(downChan)) { - if ((gotBytes == 0) && (dataPtr->flags & CHANNEL_ASYNC)) { - *errorCodePtr = EWOULDBLOCK; - gotBytes = -1; - } - break; - } - if (dataPtr->readIsFlushed) { /* * Already flushed, nothing to do anymore. -- cgit v0.12 From 055e5320555047ffa7b6a3c635374020ffbcd3ac Mon Sep 17 00:00:00 2001 From: dgp Date: Sat, 17 May 2014 03:38:28 +0000 Subject: Simplify the inputProc of [testchannel transform]. --- generic/tclIOGT.c | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/generic/tclIOGT.c b/generic/tclIOGT.c index 6cc33eb..9c4347d 100644 --- a/generic/tclIOGT.c +++ b/generic/tclIOGT.c @@ -683,39 +683,34 @@ TransformInputProc( read = Tcl_ReadRaw(downChan, buf, toRead); if (read < 0) { - /* - * Report errors to caller. EAGAIN is a special situation. If we - * had some data before we report that instead of the request to - * re-try. - */ - int error = Tcl_GetErrno(); + if (Tcl_InputBlocked(downChan) && (gotBytes > 0)) { + /* + * Zero bytes available from downChan because blocked. + * But nonzero bytes already copied, so total is a + * valid blocked short read. Return to caller. + */ - if ((error == EAGAIN) && (gotBytes > 0)) { break; } - *errorCodePtr = error; + /* + * Either downChan is not blocked (there's a real error). + * or it is and there are no bytes copied yet. In either + * case we want to pass the "error" along to the caller, + * either to report an error, or to signal to the caller + * that zero bytes are available because blocked. + */ + + *errorCodePtr = Tcl_GetErrno(); gotBytes = -1; break; } else if (read == 0) { + /* - * Check wether we hit on EOF in the underlying channel or not. If - * not differentiate between blocking and non-blocking modes. In - * non-blocking mode we ran temporarily out of data. Signal this - * to the caller via EWOULDBLOCK and error return (-1). In the - * other cases we simply return what we got and let the caller - * wait for more. On the other hand, if we got an EOF we have to - * convert and flush all waiting partial data. + * Zero returned from Tcl_ReadRaw() always indicates EOF + * on the down channel. */ - if (!Tcl_Eof(downChan)) { - if ((gotBytes == 0) && (dataPtr->flags & CHANNEL_ASYNC)) { - *errorCodePtr = EWOULDBLOCK; - gotBytes = -1; - } - break; - } - if (dataPtr->readIsFlushed) { /* * Already flushed, nothing to do anymore. -- cgit v0.12 From fae66563f5f7dc9c3633a4f4d2e46e46c4ca7afc Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 19 May 2014 18:08:05 +0000 Subject: Simplify ReflectInput(). Also stop intruding on channel internals with direct clearing of CHANNEL_EOF flag. --- generic/tclIORTrans.c | 52 ++++++++++++++++----------------------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/generic/tclIORTrans.c b/generic/tclIORTrans.c index b9dd1d6..e6e552f 100644 --- a/generic/tclIORTrans.c +++ b/generic/tclIORTrans.c @@ -1136,50 +1136,36 @@ ReflectInput( readBytes = Tcl_ReadRaw(rtPtr->parent, (char *) Tcl_SetByteArrayLength(bufObj, toRead), toRead); if (readBytes < 0) { - /* - * Report errors to caller. The state of the seek system is - * unchanged! - */ + if (Tcl_InputBlocked(rtPtr->parent) && (gotBytes > 0)) { - if ((Tcl_GetErrno() == EAGAIN) && (gotBytes > 0)) { /* - * EAGAIN is a special situation. If we had some data before - * we report that instead of the request to re-try. + * Down channel is blocked and offers zero additional bytes. + * The nonzero gotBytes already returned makes the total + * operation a valid short read. Return to caller. */ goto stop; } + /* + * Either the down channel is not blocked (a real error) + * or it is and there are gotBytes==0 byte copied so far. + * In either case, pass up the error, so we either report + * any real error, or do not mistakenly signal EOF by + * returning 0 to the caller. + */ + *errorCodePtr = Tcl_GetErrno(); goto error; } if (readBytes == 0) { + /* - * Check wether we hit on EOF in 'parent' or not. If not - * differentiate between blocking and non-blocking modes. In - * non-blocking mode we ran temporarily out of data. Signal this - * to the caller via EWOULDBLOCK and error return (-1). In the - * other cases we simply return what we got and let the caller - * wait for more. On the other hand, if we got an EOF we have to - * convert and flush all waiting partial data. + * Zero returned from Tcl_ReadRaw() always indicates EOF + * on the down channel. */ - - if (!Tcl_Eof(rtPtr->parent)) { - /* - * The state of the seek system is unchanged! - */ - - if ((gotBytes == 0) && rtPtr->nonblocking) { - *errorCodePtr = EWOULDBLOCK; - goto error; - } - goto stop; - } else { - /* - * Eof in parent. - */ - + if (rtPtr->readIsDrained) { goto stop; } @@ -1203,13 +1189,7 @@ ReflectInput( goto stop; } - /* - * Reset eof, force caller to drain result buffer. - */ - - ((Channel *) rtPtr->parent)->state->flags &= ~CHANNEL_EOF; continue; /* at: while (toRead > 0) */ - } } /* readBytes == 0 */ /* -- cgit v0.12 From ab71a62c9e9b8c57834e4b4deb60b59596ad9586 Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 19 May 2014 18:29:26 +0000 Subject: Same improvements to the zlib transform operations. --- generic/tclZlib.c | 45 +++++---------------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/generic/tclZlib.c b/generic/tclZlib.c index 9bceb4c..2e27303 100644 --- a/generic/tclZlib.c +++ b/generic/tclZlib.c @@ -2994,47 +2994,18 @@ ZlibTransformInput( */ if (readBytes < 0) { - /* - * Report errors to caller. The state of the seek system is - * unchanged! - */ - - if ((Tcl_GetErrno() == EAGAIN) && (gotBytes > 0)) { - /* - * EAGAIN is a special situation. If we had some data before - * we report that instead of the request to re-try. - */ + /* See ReflectInput() in tclIORTrans.c */ + if (Tcl_InputBlocked(cd->parent) && (gotBytes > 0)) { return gotBytes; } *errorCodePtr = Tcl_GetErrno(); return -1; - } else if (readBytes == 0) { - /* - * Check wether we hit on EOF in 'parent' or not. If not, - * differentiate between blocking and non-blocking modes. In - * non-blocking mode we ran temporarily out of data. Signal this - * to the caller via EWOULDBLOCK and error return (-1). In the - * other cases we simply return what we got and let the caller - * wait for more. On the other hand, if we got an EOF we have to - * convert and flush all waiting partial data. - */ - - if (!Tcl_Eof(cd->parent)) { - /* - * The state of the seek system is unchanged! - */ - - if ((gotBytes == 0) && (cd->flags & ASYNC)) { - *errorCodePtr = EWOULDBLOCK; - return -1; - } - return gotBytes; - } - + } + if (readBytes == 0) { /* - * (Semi-)Eof in parent. + * Eof in parent. * * Now this is a bit different. The partial data waiting is * converted and returned. @@ -3052,12 +3023,6 @@ ZlibTransformInput( return gotBytes; } - - /* - * Reset eof, force caller to drain result buffer. - */ - - ((Channel *) cd->parent)->state->flags &= ~CHANNEL_EOF; } else /* readBytes > 0 */ { /* * Transform the read chunk, which was not empty. Anything we get -- cgit v0.12 From 69c75114013bbd847b7956f6bf3cd1e432760c18 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 21 May 2014 10:37:10 +0000 Subject: Fix c&p errors in test descriptions --- tests/socket.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/socket.test b/tests/socket.test index 5ff2109..5c5b7c3 100644 --- a/tests/socket.test +++ b/tests/socket.test @@ -2098,7 +2098,7 @@ test socket-14.9.1 {pending [socket -async] and blocking [puts], server is IPv6} close $sock close $fd } -result {{} ok} -test socket-14.10.0 {pending [socket -async] and blocking [puts], server is IPv4} \ +test socket-14.10.0 {pending [socket -async] and nonblocking [puts], server is IPv4} \ -constraints {socket supported_inet supported_inet6} \ -setup { makeFile { @@ -2125,7 +2125,7 @@ test socket-14.10.0 {pending [socket -async] and blocking [puts], server is IPv4 close $sock close $fd } -result {{} ok} -test socket-14.10.1 {pending [socket -async] and blocking [puts], server is IPv6} \ +test socket-14.10.1 {pending [socket -async] and nonblocking [puts], server is IPv6} \ -constraints {socket supported_inet supported_inet6} \ -setup { makeFile { @@ -2152,7 +2152,7 @@ test socket-14.10.1 {pending [socket -async] and blocking [puts], server is IPv6 close $sock close $fd } -result {{} ok} -test socket-14.11.0 {pending [socket -async] and blocking [puts], no listener, no flush} \ +test socket-14.11.0 {pending [socket -async] and nonblocking [puts], no listener, no flush} \ -constraints {socket supported_inet supported_inet6} \ -body { set sock [socket -async localhost [randport]] @@ -2165,7 +2165,7 @@ test socket-14.11.0 {pending [socket -async] and blocking [puts], no listener, n catch {close $sock} unset x } -result {socket is not connected} -returnCodes 1 -test socket-14.11.1 {pending [socket -async] and blocking [puts], no listener, flush} \ +test socket-14.11.1 {pending [socket -async] and nonblocking [puts], no listener, flush} \ -constraints {socket supported_inet supported_inet6} \ -body { set sock [socket -async localhost [randport]] -- cgit v0.12 From 550f78e490719e4b5eaab09efdf219df2330be20 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Wed, 21 May 2014 14:37:57 +0000 Subject: Fix gcc warning (signed-unsigned compare) --- generic/tclIO.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index c6bb5e3..0c13bc0 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -5797,7 +5797,7 @@ ReadChars( * for sizing receiving buffers. */ - int toRead = ((unsigned) charsToRead > srcLen) ? srcLen : charsToRead; + int toRead = ((charsToRead<0)||(charsToRead > srcLen)) ? srcLen : charsToRead; /* * 'factor' is how much we guess that the bytes in the source buffer will -- cgit v0.12 From b54496ebcaeecd111e6aa8bbcb5b07518838b25f Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 21 May 2014 21:32:44 +0000 Subject: Update dict man page to state that [dict set] returns the updated dictionary value. --- doc/dict.n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/dict.n b/doc/dict.n index 77c460b..a75d8fb 100644 --- a/doc/dict.n +++ b/doc/dict.n @@ -202,7 +202,7 @@ This operation takes the name of a variable containing a dictionary value and places an updated dictionary value in that variable containing a mapping from the given key to the given value. When multiple keys are present, this operation creates or updates a chain -of nested dictionaries. +of nested dictionaries. The updated dictionary value is returned. .TP \fBdict size \fIdictionaryValue\fR . -- cgit v0.12 From 1e2ced92480ce68bbf628c8848e0d41bd18521e2 Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 21 May 2014 21:40:09 +0000 Subject: Ditto [dict unset]. --- doc/dict.n | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/dict.n b/doc/dict.n index a75d8fb..32d7ea8 100644 --- a/doc/dict.n +++ b/doc/dict.n @@ -216,7 +216,8 @@ dictionary value in that variable that does not contain a mapping for the given key. Where multiple keys are present, this describes a path through nested dictionaries to the mapping to remove. At least one key must be specified, but the last key on the key-path need not exist. -All other components on the path must exist. +All other components on the path must exist. The updated dictionary +value is returned. .TP \fBdict update \fIdictionaryVariable key varName \fR?\fIkey varName ...\fR? \fIbody\fR . -- cgit v0.12 From 165042b1e0a2ca0d5acee8dbfdbf151978998ea4 Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 21 May 2014 22:00:52 +0000 Subject: Ditto [dict append], [dict incr], and [dict lappend]. Update description of [dict create] to explicitly state that it returns the new dictionary. --- doc/dict.n | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/dict.n b/doc/dict.n index 32d7ea8..3bb5465 100644 --- a/doc/dict.n +++ b/doc/dict.n @@ -25,11 +25,12 @@ below for a description), depending on \fIoption\fR. The legal This appends the given string (or strings) to the value that the given key maps to in the dictionary value contained in the given variable, writing the resulting dictionary value back to that variable. -Non-existent keys are treated as if they map to an empty string. +Non-existent keys are treated as if they map to an empty string. The +updated dictionary value is returned. .TP \fBdict create \fR?\fIkey value ...\fR? . -Create a new dictionary that contains each of the key/value mappings +Return a new dictionary that contains each of the key/value mappings listed as arguments (keys and values alternating, with each key being followed by its associated value.) .TP @@ -121,7 +122,8 @@ not specified) to the value that the given key maps to in the dictionary value contained in the given variable, writing the resulting dictionary value back to that variable. Non-existent keys are treated as if they map to 0. It is an error to increment a value -for an existing key if that value is not an integer. +for an existing key if that value is not an integer. The updated +dictionary value is returned. .TP \fBdict info \fIdictionaryValue\fR . @@ -145,7 +147,8 @@ to in the dictionary value contained in the given variable, writing the resulting dictionary value back to that variable. Non-existent keys are treated as if they map to an empty list, and it is legal for there to be no items to append to the list. It is an error for the -value that the key maps to to not be representable as a list. +value that the key maps to to not be representable as a list. The +updated dictionary value is returned. .TP \fBdict map \fR{\fIkeyVar valueVar\fR} \fIdictionaryValue body\fR . -- cgit v0.12 From dced768bbaa8b761aa86cf4b8f086039502a7b7d Mon Sep 17 00:00:00 2001 From: andreask Date: Thu, 22 May 2014 17:17:12 +0000 Subject: Workarounds and fixes for wrapped executables on various platforms regarding the handling of wrapped dynamic libraries. The basic flow of operation is to copy such libraries into a temp file, hand them to the OS loader for processing, and then to delete them immediately, to prevent them from being accessible to other executables. On platforms where that is not possible the library is left in place and things are arranged to delete it on regular process exit. An example of the latter are older revisions of HPUX which report that the file is busy when trying to delete it. Younger revisions of HPUX have changed to allow the deletion, but are also buggy, the OS loader mangles its data structures so that a second library loaded in this manner fails. More recently it was found that Linux which is usually ok with deleting the file and gets everything right shows the same trouble as modern HPUX when the "docker" containerization system is involved, or more specifically the AUFS in use there. Deleting the loaded library file mangles data structures and breaks loading of the following libraries. For a demonstration which does not involve Tcl at all see the ticket https://github.com/dotcloud/docker/issues/1911 in the docker tracker. This of course breaks the use of wrapped executables within docker containers. This commit introduces the function TclSkipUnlink() which centralizes the handling of such exceptions to unlinking the library after unload, and provides code handling the known cases. IOW HPUX is generally forced to not unlink, and ditto when we detect that the copied library file resides within an AUFS. The latter must however be explicitly activated by setting the define -DTCL_TEMPLOAD_NO_UNLINK during build. We still need proper configure tests to set it on the relevant platforms (i.e. Linux). The AUFS detection and handling can be overridden by the environment variable TCL_TEMPLOAD_NO_UNLINK which can force the behaviour either way (skip or not). In case the user knows best, or wishes to test if the problem with AUFS has been fixed. --- generic/tclIOUtil.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/generic/tclIOUtil.c b/generic/tclIOUtil.c index 295e313..3d33992 100644 --- a/generic/tclIOUtil.c +++ b/generic/tclIOUtil.c @@ -24,6 +24,12 @@ #endif #include "tclFileSystem.h" +#ifdef TCL_TEMPLOAD_NO_UNLINK +#ifndef NO_FSTATFS +#include +#endif +#endif + /* * struct FilesystemRecord -- * @@ -3149,6 +3155,83 @@ Tcl_FSLoadFile( typedef int (Tcl_FSLoadFileProc2) (Tcl_Interp *interp, Tcl_Obj *pathPtr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr, int flags); +/* + * Workaround for issue with modern HPUX which do allow the unlink (no ETXTBSY + * error) yet somehow trash some internal data structures which prevents the + * second and further shared libraries from getting properly loaded. Only the + * first is ok. We try to get around the issue by not unlinking, + * i.e. emulating the behaviour of the older HPUX which denied removal. + * + * Doing the unlink is also an issue within docker containers, whose AUFS + * bungles this as well, see + * https://github.com/dotcloud/docker/issues/1911 + * + * For these situations the change below makes the execution of the unlink + * semi-controllable at runtime. + * + * An AUFS filesystem (if it can be detected) will force avoidance of + * unlink. The env variable TCL_TEMPLOAD_NO_UNLINK allows detection of a + * users general request (unlink and not. + * + * By default the unlink is done (if not in AUFS). However if the variable is + * present and set to true (any integer > 0) then the unlink is skipped. + */ + +int +TclSkipUnlink (Tcl_Obj* shlibFile) +{ + /* Order of testing: + * 1. On hpux we generally want to skip unlink in general + * + * Outside of hpux then: + * 2. For a general user request (TCL_TEMPLOAD_NO_UNLINK present, non-empty, => int) + * 3. For general AUFS environment (statfs, if available). + * + * Ad 2: This variable can disable/override the AUFS detection, i.e. for + * testing if a newer AUFS does not have the bug any more. + * + * Ad 3: This is conditionally compiled in. Condition currently must be set manually. + * This part needs proper tests in the configure(.in). + */ + +#ifdef hpux + return 1; +#else + int skip = 0; + char* skipstr; + + skipstr = getenv ("TCL_TEMPLOAD_NO_UNLINK"); + if (skipstr && (skipstr[0] != '\0')) { + return atoi(skipstr); + } + +#ifdef TCL_TEMPLOAD_NO_UNLINK +#ifndef NO_FSTATFS + { + struct statfs fs; + /* Have fstatfs. May not have the AUFS super magic ... Indeed our build + * box is too old to have it directly in the headers. Define taken from + * http://mooon.googlecode.com/svn/trunk/linux_include/linux/aufs_type.h + * http://aufs.sourceforge.net/ + * Better reference will be gladly taken. + */ +#ifndef AUFS_SUPER_MAGIC +#define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's') +#endif /* AUFS_SUPER_MAGIC */ + if ((statfs(Tcl_GetString (shlibFile), &fs) == 0) && + (fs.f_type == AUFS_SUPER_MAGIC)) { + return 1; + } + } +#endif /* ... NO_FSTATFS */ +#endif /* ... TCL_TEMPLOAD_NO_UNLINK */ + + /* Fallback: !hpux, no EV override, no AUFS (detection, nor detected): + * Don't skip */ + return 0; +#endif /* hpux */ +} + int TclLoadFile( Tcl_Interp *interp, /* Used for error reporting. */ @@ -3353,7 +3436,9 @@ TclLoadFile( * avoids any worries about leaving the copy laying around on exit. */ - if (Tcl_FSDeleteFile(copyToPtr) == TCL_OK) { + if ( + !TclSkipUnlink (copyToPtr) && + (Tcl_FSDeleteFile(copyToPtr) == TCL_OK)) { Tcl_DecrRefCount(copyToPtr); /* -- cgit v0.12 From a7c63b96168422c0fa7abd8e16091739acf350a5 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Fri, 23 May 2014 14:59:03 +0000 Subject: eliminate two unused variables. --- win/tclWinSock.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/win/tclWinSock.c b/win/tclWinSock.c index 28dfef0..f343f82 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -1233,7 +1233,6 @@ TcpGetOptionProc( char host[NI_MAXHOST], port[NI_MAXSERV]; SOCKET sock; size_t len = 0; - int errorCode = 0; int reverseDNS = 0; #define SUPPRESS_RDNS_VAR "::tcl::unsupported::noReverseDNS" @@ -1621,7 +1620,6 @@ TcpConnect( TcpState *statePtr) { DWORD error; - u_long flag = 1; /* Indicates nonblocking mode. */ /* * We are started with async connect and the connect notification * was not jet received -- cgit v0.12 From 34af92d1f5c5a77bb1ccb4bbd0658ab48805056d Mon Sep 17 00:00:00 2001 From: andreask Date: Fri, 23 May 2014 17:17:35 +0000 Subject: Followup on [72c54e1659]. Removed unused variable. --- generic/tclIOUtil.c | 1 - 1 file changed, 1 deletion(-) diff --git a/generic/tclIOUtil.c b/generic/tclIOUtil.c index 3d33992..82ffd88 100644 --- a/generic/tclIOUtil.c +++ b/generic/tclIOUtil.c @@ -3197,7 +3197,6 @@ TclSkipUnlink (Tcl_Obj* shlibFile) #ifdef hpux return 1; #else - int skip = 0; char* skipstr; skipstr = getenv ("TCL_TEMPLOAD_NO_UNLINK"); -- cgit v0.12 From 7e7fc66d0c49405ff64c193170207ba58c33301f Mon Sep 17 00:00:00 2001 From: dgp Date: Sat, 24 May 2014 19:56:37 +0000 Subject: Comment out lines of test io-53.4 that appear to do nothing of any value. --- tests/io.test | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/io.test b/tests/io.test index a2e2397..c7da8e6 100644 --- a/tests/io.test +++ b/tests/io.test @@ -7120,17 +7120,17 @@ test io-53.4 {CopyData: background write overflow} {stdio unix openpipe fileeven for {set x 0} {$x < 12} {incr x} { append big $big } - file delete $path(test1) +# file delete $path(test1) file delete $path(pipe) set f1 [open $path(pipe) w] puts $f1 { puts ready fcopy stdin stdout -command { set x } vwait x - set f [open $path(test1) w] - fconfigure $f -translation lf - puts $f "done" - close $f +# set f [open $path(test1) w] +# fconfigure $f -translation lf +# puts $f "done" +# close $f } close $f1 set f1 [open "|[list [interpreter] $path(pipe)]" r+] -- cgit v0.12 From 00ef775fc158959acc2c14c5ff3568e1f86fe538 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 27 May 2014 13:28:41 +0000 Subject: Move code that can only matter in the first loop iteration out of the loop. --- generic/tclIO.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 9e1a723..4e325ba 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2510,12 +2510,6 @@ FlushChannel( return -1; } - /* - * Loop over the queued buffers and attempt to flush as much as possible - * of the queued output to the channel. - */ - - while (1) { /* * If the queue is empty and there is a ready current buffer, OR if * the current buffer is full, then move the current buffer to the @@ -2536,7 +2530,6 @@ FlushChannel( statePtr->outQueueTail = statePtr->curOutPtr; statePtr->curOutPtr = NULL; } - bufPtr = statePtr->outQueueHead; /* * If we are not being called from an async flush and an async flush @@ -2547,13 +2540,13 @@ FlushChannel( return 0; } - /* - * If the output queue is still empty, break out of the while loop. - */ + /* + * Loop over the queued buffers and attempt to flush as much as possible + * of the queued output to the channel. + */ - if (bufPtr == NULL) { - break; /* Out of the "while (1)". */ - } + while (statePtr->outQueueHead) { + bufPtr = statePtr->outQueueHead; /* * Produce the output on the channel. -- cgit v0.12 From 1c2468956cac48003a15f5984176963d5135d340 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 28 May 2014 16:54:02 +0000 Subject: Increase size of test io-29.34 so that it more portably tests the case where the OS networking machinery gets backed up and blocks. Added several TODO comments on potential simplifications. --- generic/tclIO.c | 9 +++++++++ tests/io.test | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 4e325ba..9e0d7f1 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2555,6 +2555,7 @@ FlushChannel( PreserveChannelBuffer(bufPtr); toWrite = BytesLeft(bufPtr); if (toWrite == 0) { + /* TODO: This cannot happen. */ written = 0; } else { written = (chanPtr->typePtr->outputProc)(chanPtr->instanceData, @@ -2667,6 +2668,8 @@ FlushChannel( ReleaseChannelBuffer(bufPtr); continue; } else { + /* TODO: Consider detecting and reacting to short writes + * on blocking channels. Ought not happen. See iocmd-24.2. */ wroteSome = 1; } @@ -2700,6 +2703,12 @@ FlushChannel( ResetFlag(statePtr, BG_FLUSH_SCHEDULED); (chanPtr->typePtr->watchProc)(chanPtr->instanceData, statePtr->interestMask); + } else { + /* TODO: If code reaches this point, it means a writable + * event is being handled on the channel, but the channel + * could not in fact be written to. This ought not happen, + * but Unix pipes appear to act this way (see io-53.4). + * Also can imagine broken reflected channels. */ } } diff --git a/tests/io.test b/tests/io.test index c7da8e6..f1248b9 100644 --- a/tests/io.test +++ b/tests/io.test @@ -2786,7 +2786,7 @@ test io-29.34 {Tcl_Close, async flush on close, using sockets} {socket tempNotMa variable x running set l abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz proc writelots {s l} { - for {set i 0} {$i < 2000} {incr i} { + for {set i 0} {$i < 9000} {incr i} { puts $s $l } } @@ -2817,7 +2817,7 @@ test io-29.34 {Tcl_Close, async flush on close, using sockets} {socket tempNotMa close $ss vwait [namespace which -variable x] set c -} 2000 +} 9000 test io-29.35 {Tcl_Close vs fileevent vs multiple interpreters} {socket tempNotMac fileevent} { # On Mac, this test screws up sockets such that subsequent tests using port 2828 # either cause errors or panic(). -- cgit v0.12 From d6e3f0435451305fe0e8da53490b9c56517db94f Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 28 May 2014 17:14:11 +0000 Subject: Expand the IsBufferFull() macro to check non-NULL bufPtr.. --- generic/tclIO.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 9e0d7f1..17efa1e 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -12,6 +12,7 @@ * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ +#undef NDEBUG #include "tclInt.h" #include "tclIO.h" #include @@ -284,7 +285,7 @@ static int WillRead(Channel *chanPtr); #define IsBufferEmpty(bufPtr) ((bufPtr)->nextAdded == (bufPtr)->nextRemoved) -#define IsBufferFull(bufPtr) ((bufPtr)->nextAdded >= (bufPtr)->bufLength) +#define IsBufferFull(bufPtr) ((bufPtr) && (bufPtr)->nextAdded >= (bufPtr)->bufLength) #define IsBufferOverflowing(bufPtr) ((bufPtr)->nextAdded > (bufPtr)->bufLength) @@ -2516,8 +2517,7 @@ FlushChannel( * queue. */ - if (((statePtr->curOutPtr != NULL) && - IsBufferFull(statePtr->curOutPtr)) + if (IsBufferFull(statePtr->curOutPtr) || (GotFlag(statePtr, BUFFER_READY) && (statePtr->outQueueHead == NULL))) { ResetFlag(statePtr, BUFFER_READY); @@ -2531,6 +2531,8 @@ FlushChannel( statePtr->curOutPtr = NULL; } + assert(!IsBufferFull(statePtr->curOutPtr)); + /* * If we are not being called from an async flush and an async flush * is active, we just return without producing any output. @@ -6122,9 +6124,8 @@ GetInput( */ bufPtr = statePtr->inQueueTail; - if ((bufPtr != NULL) && !IsBufferFull(bufPtr)) { - toRead = SpaceLeft(bufPtr); - } else { + + if ((bufPtr == NULL) || IsBufferFull(bufPtr)) { bufPtr = statePtr->saveInBufPtr; statePtr->saveInBufPtr = NULL; @@ -6154,6 +6155,8 @@ GetInput( statePtr->inQueueTail->nextPtr = bufPtr; } statePtr->inQueueTail = bufPtr; + } else { + toRead = SpaceLeft(bufPtr); } PreserveChannelBuffer(bufPtr); @@ -8827,7 +8830,7 @@ DoRead( /* If there is no full buffer, attempt to create and/or fill one. */ - while (bufPtr == NULL || !IsBufferFull(bufPtr)) { + while (!IsBufferFull(bufPtr)) { int code; moreData: -- cgit v0.12 From a6b4426c1c32f4aefd4a2dc2d6aa60a756d1a586 Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 28 May 2014 18:24:56 +0000 Subject: Further simplifications to FlushChannel(). This makes clear the BUFFER_READY flag serves no necessary purpose, so it is removed. --- generic/tclIO.c | 114 +++++++++++++++++++------------------------------------- generic/tclIO.h | 5 --- 2 files changed, 38 insertions(+), 81 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 17efa1e..911fa97 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -1149,15 +1149,6 @@ Tcl_UnregisterChannel( */ if (statePtr->refCount <= 0) { - /* - * Ensure that if there is another buffer, it gets flushed whether or - * not we are doing a background flush. - */ - - if ((statePtr->curOutPtr != NULL) && - IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } Tcl_Preserve((ClientData)statePtr); if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { /* @@ -2491,8 +2482,6 @@ FlushChannel( ChannelState *statePtr = chanPtr->state; /* State of the channel stack. */ ChannelBuffer *bufPtr; /* Iterates over buffered output queue. */ - int toWrite; /* Amount of output data in current buffer - * available to be written. */ int written; /* Amount of output data actually written in * current round. */ int errorCode = 0; /* Stores POSIX error codes from channel @@ -2511,36 +2500,42 @@ FlushChannel( return -1; } - /* - * If the queue is empty and there is a ready current buffer, OR if - * the current buffer is full, then move the current buffer to the - * queue. - */ + /* + * Should we shift the current output buffer over to the output queue? + * First check that there are bytes in it. If so then... + * If the output queue is empty, then yes, trusting the caller called + * us only when written bytes ought to be flushed. + * If the current output buffer is full, then yes, so we can meet + * the post-condition that on a successful return to caller we've + * left space in the current output buffer for more writing (the flush + * call was to make new room). + * Otherwise, no. Keep the current output buffer where it is so more + * can be written to it, possibly filling it, to promote more efficient + * buffer usage. + */ - if (IsBufferFull(statePtr->curOutPtr) - || (GotFlag(statePtr, BUFFER_READY) && - (statePtr->outQueueHead == NULL))) { - ResetFlag(statePtr, BUFFER_READY); - statePtr->curOutPtr->nextPtr = NULL; - if (statePtr->outQueueHead == NULL) { - statePtr->outQueueHead = statePtr->curOutPtr; - } else { - statePtr->outQueueTail->nextPtr = statePtr->curOutPtr; - } - statePtr->outQueueTail = statePtr->curOutPtr; - statePtr->curOutPtr = NULL; + bufPtr = statePtr->curOutPtr; + if (bufPtr && BytesLeft(bufPtr) && /* Keep empties off queue */ + (statePtr->outQueueHead == NULL || IsBufferFull(bufPtr))) { + if (statePtr->outQueueHead == NULL) { + statePtr->outQueueHead = bufPtr; + } else { + statePtr->outQueueTail->nextPtr = bufPtr; } + statePtr->outQueueTail = bufPtr; + statePtr->curOutPtr = NULL; + } assert(!IsBufferFull(statePtr->curOutPtr)); - /* - * If we are not being called from an async flush and an async flush - * is active, we just return without producing any output. - */ + /* + * If we are not being called from an async flush and an async flush + * is active, we just return without producing any output. + */ - if (!calledFromAsyncFlush && GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { - return 0; - } + if (!calledFromAsyncFlush && GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { + return 0; + } /* * Loop over the queued buffers and attempt to flush as much as possible @@ -2555,14 +2550,8 @@ FlushChannel( */ PreserveChannelBuffer(bufPtr); - toWrite = BytesLeft(bufPtr); - if (toWrite == 0) { - /* TODO: This cannot happen. */ - written = 0; - } else { - written = (chanPtr->typePtr->outputProc)(chanPtr->instanceData, - RemovePoint(bufPtr), toWrite, &errorCode); - } + written = (chanPtr->typePtr->outputProc)(chanPtr->instanceData, + RemovePoint(bufPtr), BytesLeft(bufPtr), &errorCode); /* * If the write failed completely attempt to start the asynchronous @@ -2668,7 +2657,7 @@ FlushChannel( DiscardOutputQueued(statePtr); ReleaseChannelBuffer(bufPtr); - continue; + break; } else { /* TODO: Consider detecting and reacting to short writes * on blocking channels. Ought not happen. See iocmd-24.2. */ @@ -2689,7 +2678,7 @@ FlushChannel( RecycleBuffer(statePtr, bufPtr, 0); } ReleaseChannelBuffer(bufPtr); - } /* Closes "while (1)". */ + } /* Closes "while". */ /* * If we wrote some data while flushing in the background, we are done. @@ -3256,14 +3245,6 @@ Tcl_Close( ResetFlag(statePtr, CHANNEL_INCLOSE); /* - * Ensure that the last output buffer will be flushed. - */ - - if ((statePtr->curOutPtr != NULL) && IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - - /* * If this channel supports it, close the read side, since we don't need * it anymore and this will help avoid deadlocks on some channel types. */ @@ -3673,10 +3654,10 @@ static int WillRead(Channel *chanPtr) } if ((chanPtr->typePtr->seekProc != NULL) && (Tcl_OutputBuffered((Tcl_Channel) chanPtr) > 0)) { - if ((chanPtr->state->curOutPtr != NULL) - && IsBufferReady(chanPtr->state->curOutPtr)) { - SetFlag(chanPtr->state, BUFFER_READY); - } + + /* TODO: Consider when channel is nonblocking and this + * FlushChannel() call may not finish the task of shoving + * bytes out. Then what? */ if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; } @@ -3858,7 +3839,6 @@ Write( } if ((flushed < total) && (GotFlag(statePtr, CHANNEL_UNBUFFERED) || (needNlFlush && GotFlag(statePtr, CHANNEL_LINEBUFFERED)))) { - SetFlag(statePtr, BUFFER_READY); if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; } @@ -5977,14 +5957,6 @@ Tcl_Flush( return -1; } - /* - * Force current output buffer to be output also. - */ - - if ((statePtr->curOutPtr != NULL) && IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - result = FlushChannel(NULL, chanPtr, 0); if (result != 0) { return TCL_ERROR; @@ -6298,15 +6270,6 @@ Tcl_Seek( } /* - * If there is data buffered in statePtr->curOutPtr then mark the channel - * as ready to flush before invoking FlushChannel. - */ - - if ((statePtr->curOutPtr != NULL) && IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - - /* * If the flush fails we cannot recover the original position. In that * case the seek is not attempted because we do not know where the access * position is - instead we return the error. FlushChannel has already @@ -10385,7 +10348,6 @@ DumpFlags( ChanFlag('n', CHANNEL_NONBLOCKING); ChanFlag('l', CHANNEL_LINEBUFFERED); ChanFlag('u', CHANNEL_UNBUFFERED); - ChanFlag('R', BUFFER_READY); ChanFlag('F', BG_FLUSH_SCHEDULED); ChanFlag('c', CHANNEL_CLOSED); ChanFlag('E', CHANNEL_EOF); diff --git a/generic/tclIO.h b/generic/tclIO.h index b8fb5be..59182ec 100644 --- a/generic/tclIO.h +++ b/generic/tclIO.h @@ -227,11 +227,6 @@ typedef struct ChannelState { * flushed after every newline. */ #define CHANNEL_UNBUFFERED (1<<5) /* Output to the channel must always * be flushed immediately. */ -#define BUFFER_READY (1<<6) /* Current output buffer (the - * curOutPtr field in the channel - * structure) should be output as soon - * as possible even though it may not - * be full. */ #define BG_FLUSH_SCHEDULED (1<<7) /* A background flush of the queued * output buffers has been * scheduled. */ -- cgit v0.12 From 8dc54fa3e0120b8916399d8eb7c07c8dbf3810cb Mon Sep 17 00:00:00 2001 From: dgp Date: Wed, 28 May 2014 18:49:02 +0000 Subject: Update comment to explain assumptions. --- generic/tclIO.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 911fa97..ccd5708 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -3655,9 +3655,16 @@ static int WillRead(Channel *chanPtr) if ((chanPtr->typePtr->seekProc != NULL) && (Tcl_OutputBuffered((Tcl_Channel) chanPtr) > 0)) { - /* TODO: Consider when channel is nonblocking and this - * FlushChannel() call may not finish the task of shoving - * bytes out. Then what? */ + /* + * CAVEAT - The assumption here is that FlushChannel() will + * push out the bytes of any writes that are in progress. + * Since this is a seekable channel, we assume it is not one + * that can block and force bg flushing. Channels we know that + * can do that -- sockets, pipes -- are not seekable. If the + * assumption is wrong, more drastic measures may be required here + * like temporarily setting the channel into blocking mode. + */ + if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; } -- cgit v0.12 From ea994aa1957bd3faea8f4cdf2ae290d102ae1fe8 Mon Sep 17 00:00:00 2001 From: dgp Date: Sat, 31 May 2014 02:30:53 +0000 Subject: Correct the interest masks in the Tcl_CreateFileHandler() calls in PipeWatchProc(). When we are interested in both readable and writable events of a command pipeline channel, we only want the readable from the read end of the pipe, and the writable from the write end of the pipe. --- generic/tclIO.c | 17 ++++++++++++----- tests/io.test | 6 ------ unix/tclUnixPipe.c | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/generic/tclIO.c b/generic/tclIO.c index 0716074..1c4a5b3 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -2694,11 +2694,18 @@ FlushChannel( (chanPtr->typePtr->watchProc)(chanPtr->instanceData, statePtr->interestMask); } else { - /* TODO: If code reaches this point, it means a writable - * event is being handled on the channel, but the channel - * could not in fact be written to. This ought not happen, - * but Unix pipes appear to act this way (see io-53.4). - * Also can imagine broken reflected channels. */ + + /* + * When we are calledFromAsyncFlush, that means a writable + * state on the channel triggered the call, so we should be + * able to write something. Either we did write something + * and wroteSome should be set, or there was nothing left to + * write in this call, and we've completed the BG flush. + * These are the two cases above. If we get here, that means + * there is some kind failure in the writable event machinery. + */ + + assert(!calledFromAsyncFlush); } } diff --git a/tests/io.test b/tests/io.test index f1248b9..2296986 100644 --- a/tests/io.test +++ b/tests/io.test @@ -7120,17 +7120,12 @@ test io-53.4 {CopyData: background write overflow} {stdio unix openpipe fileeven for {set x 0} {$x < 12} {incr x} { append big $big } -# file delete $path(test1) file delete $path(pipe) set f1 [open $path(pipe) w] puts $f1 { puts ready fcopy stdin stdout -command { set x } vwait x -# set f [open $path(test1) w] -# fconfigure $f -translation lf -# puts $f "done" -# close $f } close $f1 set f1 [open "|[list [interpreter] $path(pipe)]" r+] @@ -7138,7 +7133,6 @@ test io-53.4 {CopyData: background write overflow} {stdio unix openpipe fileeven fconfigure $f1 -blocking 0 puts $f1 $big flush $f1 - after 500 set result "" fileevent $f1 read [namespace code { append result [read $f1 1024] diff --git a/unix/tclUnixPipe.c b/unix/tclUnixPipe.c index d0a5e53..57be08f 100644 --- a/unix/tclUnixPipe.c +++ b/unix/tclUnixPipe.c @@ -1125,7 +1125,7 @@ PipeWatchProc( if (psPtr->inFile) { newmask = mask & (TCL_READABLE | TCL_EXCEPTION); if (newmask) { - Tcl_CreateFileHandler(GetFd(psPtr->inFile), mask, + Tcl_CreateFileHandler(GetFd(psPtr->inFile), newmask, (Tcl_FileProc *) Tcl_NotifyChannel, (ClientData) psPtr->channel); } else { @@ -1135,7 +1135,7 @@ PipeWatchProc( if (psPtr->outFile) { newmask = mask & (TCL_WRITABLE | TCL_EXCEPTION); if (newmask) { - Tcl_CreateFileHandler(GetFd(psPtr->outFile), mask, + Tcl_CreateFileHandler(GetFd(psPtr->outFile), newmask, (Tcl_FileProc *) Tcl_NotifyChannel, (ClientData) psPtr->channel); } else { -- cgit v0.12