diff options
52 files changed, 2503 insertions, 1131 deletions
diff --git a/compat/stdlib.h b/compat/stdlib.h index bb0f133..2f7eaf4 100644 --- a/compat/stdlib.h +++ b/compat/stdlib.h @@ -21,14 +21,18 @@ extern void abort(void); extern double atof(const char *string); extern int atoi(const char *string); extern long atol(const char *string); -extern char * calloc(unsigned int numElements, unsigned int size); +extern void * calloc(unsigned long numElements, unsigned long size); extern void exit(int status); -extern int free(char *blockPtr); +extern void free(void *blockPtr); extern char * getenv(const char *name); -extern char * malloc(unsigned int numBytes); -extern void qsort(void *base, int n, int size, int (*compar)( +extern void * malloc(unsigned long numBytes); +extern void qsort(void *base, unsigned long n, unsigned long size, int (*compar)( const void *element1, const void *element2)); -extern char * realloc(char *ptr, unsigned int numBytes); +extern void * realloc(void *ptr, unsigned long numBytes); +extern char * realpath(const char *path, char *resolved_path); +extern int mkstemps(char *templ, int suffixlen); +extern int mkstemp(char *templ); +extern char * mkdtemp(char *templ); extern long strtol(const char *string, char **endPtr, int base); extern unsigned long strtoul(const char *string, char **endPtr, int base); diff --git a/doc/encoding.n b/doc/encoding.n index 2277f9d..c1dbf27 100644 --- a/doc/encoding.n +++ b/doc/encoding.n @@ -117,7 +117,7 @@ which is the Hiragana letter HA. The following example detects the error location in an incomplete UTF-8 sequence: .PP .CS -% set s [\fBencoding convertfrom\fR -failindex i utf-8 "A\xc3"] +% set s [\fBencoding convertfrom\fR -failindex i utf-8 "A\exC3"] A % set i 1 @@ -127,7 +127,7 @@ The following example detects the error location while transforming to ISO8859-1 (ISO-Latin 1): .PP .CS -% set s [\fBencoding convertto\fR -failindex i utf-8 "A\u0141"] +% set s [\fBencoding convertto\fR -failindex i utf-8 "A\eu0141"] A % set i 1 diff --git a/generic/regexec.c b/generic/regexec.c index 40839b1..7b84f0f 100644 --- a/generic/regexec.c +++ b/generic/regexec.c @@ -232,13 +232,15 @@ exec( v->err = 0; assert(v->g->ntree >= 0); n = v->g->ntree; - if (n <= LOCALDFAS) + if (n <= LOCALDFAS) { v->subdfas = subdfas; - else + } else { v->subdfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *)); + } if (v->subdfas == NULL) { - if (v->pmatch != pmatch && v->pmatch != mat) + if (v->pmatch != pmatch && v->pmatch != mat) { FREE(v->pmatch); + } FreeVars(v); return REG_ESPACE; } @@ -275,11 +277,13 @@ exec( } n = v->g->ntree; for (i = 0; i < n; i++) { - if (v->subdfas[i] != NULL) + if (v->subdfas[i] != NULL) { freeDFA(v->subdfas[i]); + } } - if (v->subdfas != subdfas) + if (v->subdfas != subdfas) { FREE(v->subdfas); + } FreeVars(v); return st; } @@ -295,8 +299,9 @@ getsubdfa(struct vars * v, { if (v->subdfas[t->id] == NULL) { v->subdfas[t->id] = newDFA(v, &t->cnfa, &v->g->cmap, NULL); - if (ISERR()) + if (ISERR()) { return NULL; + } } return v->subdfas[t->id]; } @@ -636,10 +641,11 @@ cdissect( break; case '.': /* concatenation */ assert(t->left != NULL && t->right != NULL); - if (t->left->flags & SHORTER) /* reverse scan */ + if (t->left->flags & SHORTER) {/* reverse scan */ er = crevcondissect(v, t, begin, end); - else + } else { er = ccondissect(v, t, begin, end); + } break; case '|': /* alternation */ assert(t->left != NULL); @@ -647,10 +653,11 @@ cdissect( break; case '*': /* iteration */ assert(t->left != NULL); - if (t->left->flags & SHORTER) /* reverse scan */ + if (t->left->flags & SHORTER) {/* reverse scan */ er = creviterdissect(v, t, begin, end); - else + } else { er = citerdissect(v, t, begin, end); + } break; case '(': /* capturing */ assert(t->left != NULL && t->right == NULL); @@ -916,17 +923,20 @@ cbrdissect( assert(end > begin); tlen = end - begin; - if (tlen % brlen != 0) + if (tlen % brlen != 0) { return REG_NOMATCH; + } numreps = tlen / brlen; - if (numreps < (size_t)min || (numreps > (size_t)max && max != DUPINF)) + if (numreps < (size_t)min || (numreps > (size_t)max && max != DUPINF)) { return REG_NOMATCH; + } /* okay, compare the actual string contents */ p = begin; while (numreps-- > 0) { - if ((*v->g->compare) (brstring, p, brlen) != 0) + if ((*v->g->compare) (brstring, p, brlen) != 0) { return REG_NOMATCH; + } p += brlen; } @@ -1003,8 +1013,9 @@ citerdissect(struct vars * v, */ min_matches = t->min; if (min_matches <= 0) { - if (begin == end) + if (begin == end) { return REG_OKAY; + } min_matches = 1; } @@ -1018,8 +1029,9 @@ citerdissect(struct vars * v, * sub-match endpoints in endpts[1..max_matches]. */ max_matches = end - begin; - if (max_matches > (size_t)t->max && t->max != DUPINF) + if (max_matches > (size_t)t->max && t->max != DUPINF) { max_matches = t->max; + } if (max_matches < (size_t)min_matches) max_matches = min_matches; endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *)); @@ -1062,8 +1074,9 @@ citerdissect(struct vars * v, t->id, k, LOFF(endpts[k]))); /* k'th sub-match can no longer be considered verified */ - if (nverified >= k) + if (nverified >= k) { nverified = k - 1; + } if (endpts[k] != end) { /* haven't reached end yet, try another iteration if allowed */ @@ -1089,8 +1102,9 @@ citerdissect(struct vars * v, * number of matches, start the slow part: recurse to verify each * sub-match. We always have k <= max_matches, needn't check that. */ - if (k < min_matches) + if (k < min_matches) { goto backtrack; + } MDEBUG(("%d: verifying %d..%d\n", t->id, nverified + 1, k)); @@ -1101,8 +1115,9 @@ citerdissect(struct vars * v, nverified = i; continue; } - if (er == REG_NOMATCH) + if (er == REG_NOMATCH) { break; + } /* oops, something failed */ FREE(endpts); return er; @@ -1176,8 +1191,9 @@ creviterdissect(struct vars * v, */ min_matches = t->min; if (min_matches <= 0) { - if (begin == end) + if (begin == end) { return REG_OKAY; + } min_matches = 1; } @@ -1231,8 +1247,9 @@ creviterdissect(struct vars * v, limit++; /* if this is the last allowed sub-match, it must reach to the end */ - if ((size_t)k >= max_matches) + if ((size_t)k >= max_matches) { limit = end; + } /* try to find an endpoint for the k'th sub-match */ endpts[k] = shortest(v, d, endpts[k - 1], limit, end, @@ -1246,8 +1263,9 @@ creviterdissect(struct vars * v, t->id, k, LOFF(endpts[k]))); /* k'th sub-match can no longer be considered verified */ - if (nverified >= k) + if (nverified >= k) { nverified = k - 1; + } if (endpts[k] != end) { /* haven't reached end yet, try another iteration if allowed */ @@ -1268,8 +1286,9 @@ creviterdissect(struct vars * v, * number of matches, start the slow part: recurse to verify each * sub-match. We always have k <= max_matches, needn't check that. */ - if (k < min_matches) + if (k < min_matches) { goto backtrack; + } MDEBUG(("%d: verifying %d..%d\n", t->id, nverified + 1, k)); @@ -1280,8 +1299,9 @@ creviterdissect(struct vars * v, nverified = i; continue; } - if (er == REG_NOMATCH) + if (er == REG_NOMATCH) { break; + } /* oops, something failed */ FREE(endpts); return er; diff --git a/generic/tclBasic.c b/generic/tclBasic.c index ccaf306..0be1329 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -3079,7 +3079,7 @@ TclRenameCommand( } cmdNsPtr = cmdPtr->nsPtr; - oldFullName = Tcl_NewObj(); + TclNewObj(oldFullName); Tcl_IncrRefCount(oldFullName); Tcl_GetCommandFullName(interp, cmd, oldFullName); @@ -5108,7 +5108,7 @@ TclEvalEx( * TCL_EVAL_GLOBAL was set. */ int allowExceptions = (iPtr->evalFlags & TCL_ALLOW_EXCEPTIONS); int gotParse = 0; - size_t i, objectsUsed = 0; + TCL_HASH_TYPE i, objectsUsed = 0; /* These variables keep track of how much * state has been allocated while evaluating * the script, so that it can be freed diff --git a/generic/tclBinary.c b/generic/tclBinary.c index a5d1e29..8e5c91f 100644 --- a/generic/tclBinary.c +++ b/generic/tclBinary.c @@ -341,7 +341,7 @@ Tcl_SetByteArrayObj( * * Attempt to extract the value from objPtr in the representation * of a byte sequence. On success return the extracted byte sequence. - * On failures, return NULL and record error message and code in + * On failure, return NULL and record error message and code in * interp (if not NULL). * * Results: @@ -872,8 +872,7 @@ BinaryFormatCmd( * cursor has visited.*/ const char *errorString; const char *errorValue, *str; - size_t offset, size; - size_t length; + size_t offset, size, length; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "formatString ?arg ...?"); @@ -1379,8 +1378,7 @@ BinaryScanCmd( unsigned char *buffer; /* Start of result buffer. */ const char *errorString; const char *str; - size_t offset, size, i; - size_t length = 0; + size_t offset, size, length = 0, i; Tcl_Obj *valuePtr, *elementPtr; Tcl_HashTable numberCacheHash; @@ -1656,7 +1654,7 @@ BinaryScanCmd( if (count == BINARY_NOCOUNT) { count = 1; } - if ((count == BINARY_ALL) || (count > length - offset)) { + if ((count == BINARY_ALL) || (count > (length - offset))) { offset = length; } else { offset += count; diff --git a/generic/tclClock.c b/generic/tclClock.c index 283b87a..540fd34 100644 --- a/generic/tclClock.c +++ b/generic/tclClock.c @@ -1520,9 +1520,9 @@ GetJulianDayFromEraYearMonthDay( * Have to make sure quotient is truncated towards 0 when negative. * See above bug for details. The casts are necessary. */ - if (ym1 >= 0) + if (ym1 >= 0) { ym1o4 = ym1 / 4; - else { + } else { ym1o4 = - (int) (((unsigned int) -ym1) / 4); } #endif diff --git a/generic/tclCmdIL.c b/generic/tclCmdIL.c index 4d6419f..904996e 100644 --- a/generic/tclCmdIL.c +++ b/generic/tclCmdIL.c @@ -2904,7 +2904,7 @@ Tcl_LrepeatObjCmd( List *listRepPtr = ListRepPtr(listPtr); listRepPtr->elemCount = elementCount*objc; - dataArray = &listRepPtr->elements; + dataArray = listRepPtr->elements; } /* @@ -3091,7 +3091,7 @@ Tcl_LreverseObjCmd( resultObj = Tcl_NewListObj(elemc, NULL); listRepPtr = ListRepPtr(resultObj); listRepPtr->elemCount = elemc; - dataArray = &listRepPtr->elements; + dataArray = listRepPtr->elements; for (i=0,j=elemc-1 ; i<elemc ; i++,j--) { dataArray[j] = elemv[i]; @@ -4424,7 +4424,7 @@ Tcl_LsortObjCmd( resultPtr = Tcl_NewListObj(sortInfo.numElements * groupSize, NULL); listRepPtr = ListRepPtr(resultPtr); - newArray = &listRepPtr->elements; + newArray = listRepPtr->elements; if (group) { for (i=0; elementPtr!=NULL ; elementPtr=elementPtr->nextPtr) { idx = elementPtr->payload.index; diff --git a/generic/tclEnsemble.c b/generic/tclEnsemble.c index 0f1bad0..06cd0c5 100644 --- a/generic/tclEnsemble.c +++ b/generic/tclEnsemble.c @@ -21,12 +21,12 @@ static inline Tcl_Obj * NewNsObj(Tcl_Namespace *namespacePtr); static inline int EnsembleUnknownCallback(Tcl_Interp *interp, EnsembleConfig *ensemblePtr, size_t objc, Tcl_Obj *const objv[], Tcl_Obj **prefixObjPtr); -static int NsEnsembleImplementationCmdNR(ClientData clientData, +static int NsEnsembleImplementationCmdNR(void *clientData, Tcl_Interp *interp,size_t objc,Tcl_Obj *const objv[]); static void BuildEnsembleConfig(EnsembleConfig *ensemblePtr); static int NsEnsembleStringOrder(const void *strPtr1, const void *strPtr2); -static void DeleteEnsembleConfig(ClientData clientData); +static void DeleteEnsembleConfig(void *clientData); static void MakeCachedEnsembleCommand(Tcl_Obj *objPtr, EnsembleConfig *ensemblePtr, Tcl_HashEntry *hPtr, Tcl_Obj *fix); @@ -1682,7 +1682,7 @@ TclMakeEnsemble( int TclEnsembleImplementationCmd( - ClientData clientData, + void *clientData, Tcl_Interp *interp, size_t objc, Tcl_Obj *const objv[]) @@ -1693,7 +1693,7 @@ TclEnsembleImplementationCmd( static int NsEnsembleImplementationCmdNR( - ClientData clientData, + void *clientData, Tcl_Interp *interp, size_t objc, Tcl_Obj *const objv[]) @@ -1988,7 +1988,7 @@ NsEnsembleImplementationCmdNR( int TclClearRootEnsemble( - TCL_UNUSED(ClientData *), + TCL_UNUSED(void **), Tcl_Interp *interp, int result) { @@ -2094,7 +2094,7 @@ TclResetRewriteEnsemble( static int FreeER( - ClientData data[], + void *data[], TCL_UNUSED(Tcl_Interp *), int result) { @@ -2491,7 +2491,7 @@ ClearTable( static void DeleteEnsembleConfig( - ClientData clientData) + void *clientData) { EnsembleConfig *ensemblePtr = (EnsembleConfig *)clientData; Namespace *nsPtr = ensemblePtr->nsPtr; diff --git a/generic/tclExecute.c b/generic/tclExecute.c index 6ded5ea..9a475dd 100644 --- a/generic/tclExecute.c +++ b/generic/tclExecute.c @@ -2750,7 +2750,7 @@ TEBCresume( pc += 1; /* yield next instruction */ TEBC_YIELD(); - /* add TEBCresume for object at top of stack */ + /* add TEBCResume for object at top of stack */ return TclNRExecuteByteCode(interp, TclCompileObj(interp, OBJ_AT_TOS, NULL, 0)); @@ -4452,7 +4452,7 @@ TEBCresume( TRACE_APPEND(("ERROR: \"%.30s\" not on reachable chain\n", O2S(valuePtr))); - for (i=contextPtr->index ; i != TCL_INDEX_NONE ; i--) { + for (i = contextPtr->index ; i != TCL_INDEX_NONE ; i--) { miPtr = contextPtr->callPtr->chain + i; if (miPtr->isFilter || miPtr->mPtr->declaringClassPtr != classPtr) { diff --git a/generic/tclIO.c b/generic/tclIO.c index 50fb9dc..12f486f 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4454,8 +4454,8 @@ Write( } } } - if ((flushed < total) && (GotFlag(statePtr, CHANNEL_UNBUFFERED) || - (needNlFlush && GotFlag(statePtr, CHANNEL_LINEBUFFERED)))) { + if (((flushed < total) && GotFlag(statePtr, CHANNEL_UNBUFFERED)) || + (needNlFlush && GotFlag(statePtr, CHANNEL_LINEBUFFERED))) { if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; } @@ -4717,7 +4717,6 @@ Tcl_GetsObj( eol = dst; skip = 1; if (GotFlag(statePtr, INPUT_SAW_CR)) { - ResetFlag(statePtr, INPUT_SAW_CR); if ((eol < dstEnd) && (*eol == '\n')) { /* * Skip the raw bytes that make up the '\n'. @@ -4767,8 +4766,10 @@ Tcl_GetsObj( skip++; } eol--; + ResetFlag(statePtr, INPUT_SAW_CR); goto gotEOL; } else if (*eol == '\n') { + ResetFlag(statePtr, INPUT_SAW_CR); goto gotEOL; } } @@ -4797,7 +4798,7 @@ Tcl_GetsObj( Tcl_SetObjLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; - ResetFlag(statePtr, CHANNEL_BLOCKED); + ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR); goto done; } goto gotEOL; diff --git a/generic/tclInt.h b/generic/tclInt.h index 61c2cae..c31a4da 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2398,14 +2398,14 @@ typedef struct List { * derived from the list representation. May * be ignored if there is no string rep at * all.*/ - Tcl_Obj *elements; /* First list element; the struct is grown to + Tcl_Obj *elements[TCLFLEXARRAY]; /* First list element; the struct is grown to * accommodate all elements. */ } List; #define LIST_MAX \ - (1 + (((size_t)UINT_MAX - sizeof(List))/sizeof(Tcl_Obj *))) + (((size_t)UINT_MAX - offsetof(List, elements))/sizeof(Tcl_Obj *)) #define LIST_SIZE(numElems) \ - (sizeof(List) + (((numElems) - 1) * sizeof(Tcl_Obj *))) + (TCL_HASH_TYPE)(offsetof(List, elements) + ((numElems) * sizeof(Tcl_Obj *))) /* * Macro used to get the elements of a list object. @@ -2415,7 +2415,7 @@ typedef struct List { ((List *) (listPtr)->internalRep.twoPtrValue.ptr1) #define ListObjGetElements(listPtr, objc, objv) \ - ((objv) = &(ListRepPtr(listPtr)->elements), \ + ((objv) = ListRepPtr(listPtr)->elements, \ (objc) = ListRepPtr(listPtr)->elemCount) #define ListObjLength(listPtr, len) \ diff --git a/generic/tclInterp.c b/generic/tclInterp.c index 97ceaf5..0e707dd 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -1847,7 +1847,7 @@ AliasNRCmd( listPtr = Tcl_NewListObj(cmdc, NULL); listRep = ListRepPtr(listPtr); listRep->elemCount = cmdc; - cmdv = &listRep->elements; + cmdv = listRep->elements; prefv = &aliasPtr->objPtr; memcpy(cmdv, prefv, (prefc * sizeof(Tcl_Obj *))); diff --git a/generic/tclLink.c b/generic/tclLink.c index 839cc0c..2649d12 100644 --- a/generic/tclLink.c +++ b/generic/tclLink.c @@ -95,7 +95,7 @@ typedef struct { * Forward references to functions defined later in this file: */ -static char * LinkTraceProc(ClientData clientData,Tcl_Interp *interp, +static char * LinkTraceProc(void *clientData,Tcl_Interp *interp, const char *name1, const char *name2, int flags); static Tcl_Obj * ObjValue(Link *linkPtr); static void LinkFree(Link *linkPtr); @@ -527,7 +527,7 @@ GetUWide( Tcl_WideUInt *uwidePtr) { Tcl_WideInt *widePtr = (Tcl_WideInt *) uwidePtr; - ClientData clientData; + void *clientData; int type, intValue; if (TclGetNumberFromObj(NULL, objPtr, &clientData, &type) == TCL_OK) { @@ -631,8 +631,9 @@ SetInvalidRealFromAny( TCL_UNUSED(Tcl_Interp *), Tcl_Obj *objPtr) { + const char *str; + const char *endPtr; size_t length; - const char *str, *endPtr; str = Tcl_GetStringFromObj(objPtr, &length); if ((length == 1) && (str[0] == '.')) { @@ -643,8 +644,8 @@ SetInvalidRealFromAny( if (TclParseNumber(NULL, objPtr, NULL, str, length, &endPtr, TCL_PARSE_DECIMAL_ONLY) == TCL_OK) { /* - * If number is followed by [eE][+-]?, then it is an invalid double, - * but it could be the start of a valid double. + * If number is followed by [eE][+-]?, then it is an invalid + * double, but it could be the start of a valid double. */ if (*endPtr == 'e' || *endPtr == 'E') { @@ -667,10 +668,10 @@ SetInvalidRealFromAny( } /* - * This function checks for integer representations, which are valid when - * linking with C variables, but which are invalid in other contexts in Tcl. - * Handled are "+", "-", "", "0x", "0b", "0d" and "0o" (upper- and - * lower-case). See bug [39f6304c2e]. + * This function checks for integer representations, which are valid + * when linking with C variables, but which are invalid in other + * contexts in Tcl. Handled are "+", "-", "", "0x", "0b", "0d" and "0o" + * (upperand lowercase). See bug [39f6304c2e]. */ static int @@ -681,8 +682,8 @@ GetInvalidIntFromObj( size_t length; const char *str = Tcl_GetStringFromObj(objPtr, &length); - if ((length == 0) || - ((length == 2) && (str[0] == '0') && strchr("xXbBoOdD", str[1]))) { + if ((length == 0) || ((length == 2) && (str[0] == '0') + && strchr("xXbBoOdD", str[1]))) { *intPtr = 0; return TCL_OK; } else if ((length == 1) && strchr("+-", str[0])) { @@ -693,10 +694,10 @@ GetInvalidIntFromObj( } /* - * This function checks for double representations, which are valid when - * linking with C variables, but which are invalid in other contexts in Tcl. - * Handled are "+", "-", "", ".", "0x", "0b" and "0o" (upper- and lower-case) - * and sequences like "1e-". See bug [39f6304c2e]. + * This function checks for double representations, which are valid + * when linking with C variables, but which are invalid in other + * contexts in Tcl. Handled are "+", "-", "", ".", "0x", "0b" and "0o" + * (upper- and lowercase) and sequences like "1e-". See bug [39f6304c2e]. */ static int @@ -744,7 +745,7 @@ GetInvalidDoubleFromObj( static char * LinkTraceProc( - ClientData clientData, /* Contains information about the link. */ + void *clientData, /* Contains information about the link. */ Tcl_Interp *interp, /* Interpreter containing Tcl variable. */ TCL_UNUSED(const char *) /*name1*/, TCL_UNUSED(const char *) /*name2*/, diff --git a/generic/tclListObj.c b/generic/tclListObj.c index 0714bf9..3c7019c 100644 --- a/generic/tclListObj.c +++ b/generic/tclListObj.c @@ -20,7 +20,7 @@ static List * AttemptNewList(Tcl_Interp *interp, size_t objc, Tcl_Obj *const objv[]); -static List * NewListIntRep(size_t objc, Tcl_Obj *const objv[], size_t p); +static List * NewListInternalRep(size_t objc, Tcl_Obj *const objv[], size_t p); static void DupListInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static void FreeListInternalRep(Tcl_Obj *listPtr); static int SetListFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); @@ -49,7 +49,7 @@ const Tcl_ObjType tclListType = { /* Macros to manipulate the List internal rep */ -#define ListSetIntRep(objPtr, listRepPtr) \ +#define ListSetInternalRep(objPtr, listRepPtr) \ do { \ Tcl_ObjInternalRep ir; \ ir.twoPtrValue.ptr1 = (listRepPtr); \ @@ -58,14 +58,14 @@ const Tcl_ObjType tclListType = { Tcl_StoreInternalRep((objPtr), &tclListType, &ir); \ } while (0) -#define ListGetIntRep(objPtr, listRepPtr) \ +#define ListGetInternalRep(objPtr, listRepPtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &tclListType); \ (listRepPtr) = irPtr ? (List *)irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) -#define ListResetIntRep(objPtr, listRepPtr) \ +#define ListResetInternalRep(objPtr, listRepPtr) \ TclFetchInternalRep((objPtr), &tclListType)->twoPtrValue.ptr1 = (listRepPtr) #ifndef TCL_MIN_ELEMENT_GROWTH @@ -75,7 +75,7 @@ const Tcl_ObjType tclListType = { /* *---------------------------------------------------------------------- * - * NewListIntRep -- + * NewListInternalRep -- * * Creates a 'List' structure with space for 'objc' elements. 'objc' must * be > 0. If 'objv' is not NULL, The list is initialized with first @@ -98,7 +98,7 @@ const Tcl_ObjType tclListType = { */ static List * -NewListIntRep( +NewListInternalRep( size_t objc, Tcl_Obj *const objv[], size_t p) @@ -123,7 +123,7 @@ NewListIntRep( size_t i; listRepPtr->elemCount = objc; - elemPtrs = &listRepPtr->elements; + elemPtrs = listRepPtr->elements; for (i = 0; i < objc; i++) { elemPtrs[i] = objv[i]; Tcl_IncrRefCount(elemPtrs[i]); @@ -139,7 +139,7 @@ NewListIntRep( * * AttemptNewList -- * - * Like NewListIntRep, but additionally sets an error message on failure. + * Like NewListInternalRep, but additionally sets an error message on failure. * *---------------------------------------------------------------------- */ @@ -150,7 +150,7 @@ AttemptNewList( size_t objc, Tcl_Obj *const objv[]) { - List *listRepPtr = NewListIntRep(objc, objv, 0); + List *listRepPtr = NewListInternalRep(objc, objv, 0); if (interp != NULL && listRepPtr == NULL) { if (objc > LIST_MAX) { @@ -221,14 +221,14 @@ Tcl_NewListObj( * Create the internal rep. */ - listRepPtr = NewListIntRep(objc, objv, 1); + listRepPtr = NewListInternalRep(objc, objv, 1); /* * Now create the object. */ TclInvalidateStringRep(listPtr); - ListSetIntRep(listPtr, listRepPtr); + ListSetInternalRep(listPtr, listRepPtr); return listPtr; } #endif /* if TCL_MEM_DEBUG */ @@ -272,14 +272,14 @@ Tcl_DbNewListObj( * Create the internal rep. */ - listRepPtr = NewListIntRep(objc, objv, 1); + listRepPtr = NewListInternalRep(objc, objv, 1); /* * Now create the object. */ TclInvalidateStringRep(listPtr); - ListSetIntRep(listPtr, listRepPtr); + ListSetInternalRep(listPtr, listRepPtr); return listPtr; } @@ -334,8 +334,8 @@ Tcl_SetListObj( */ if (objc > 0) { - listRepPtr = NewListIntRep(objc, objv, 1); - ListSetIntRep(objPtr, listRepPtr); + listRepPtr = NewListInternalRep(objc, objv, 1); + ListSetInternalRep(objPtr, listRepPtr); } else { Tcl_InitStringRep(objPtr, NULL, 0); } @@ -373,7 +373,7 @@ TclListObjCopy( Tcl_Obj *copyPtr; List *listRepPtr; - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); if (NULL == listRepPtr) { if (SetListFromAny(interp, listPtr) != TCL_OK) { return NULL; @@ -412,8 +412,7 @@ TclListObjRange( size_t toIdx) /* Index of last element to include. */ { Tcl_Obj **elemPtrs; - size_t listLen; - size_t i, newLen; + size_t listLen, i, newLen; List *listRepPtr; TclListObjGetElementsM(NULL, listPtr, &listLen, &elemPtrs); @@ -516,7 +515,7 @@ Tcl_ListObjGetElements( { List *listRepPtr; - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); if (listRepPtr == NULL) { int result; @@ -532,10 +531,10 @@ Tcl_ListObjGetElements( if (result != TCL_OK) { return result; } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); } *objcPtr = listRepPtr->elemCount; - *objvPtr = &listRepPtr->elements; + *objvPtr = listRepPtr->elements; return TCL_OK; } @@ -642,7 +641,7 @@ Tcl_ListObjAppendElement( Tcl_Panic("%s called with shared object", "Tcl_ListObjAppendElement"); } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); if (listRepPtr == NULL) { int result; size_t length; @@ -656,7 +655,7 @@ Tcl_ListObjAppendElement( if (result != TCL_OK) { return result; } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); } numElems = listRepPtr->elemCount; @@ -701,7 +700,7 @@ Tcl_ListObjAppendElement( } } if (isShared || needGrow) { - Tcl_Obj **dst, **src = &listRepPtr->elements; + Tcl_Obj **dst, **src = listRepPtr->elements; /* * Either we have a shared internalrep and we must copy to write, or we @@ -729,7 +728,7 @@ Tcl_ListObjAppendElement( return TCL_ERROR; } - dst = &newPtr->elements; + dst = newPtr->elements; newPtr->refCount++; newPtr->canonicalFlag = listRepPtr->canonicalFlag; newPtr->elemCount = listRepPtr->elemCount; @@ -754,10 +753,10 @@ Tcl_ListObjAppendElement( } listRepPtr = newPtr; } - ListResetIntRep(listPtr, listRepPtr); + ListResetInternalRep(listPtr, listRepPtr); listRepPtr->refCount++; TclFreeInternalRep(listPtr); - ListSetIntRep(listPtr, listRepPtr); + ListSetInternalRep(listPtr, listRepPtr); listRepPtr->refCount--; /* @@ -765,7 +764,7 @@ Tcl_ListObjAppendElement( * the ref count for the (now shared) objPtr. */ - *(&listRepPtr->elements + listRepPtr->elemCount) = objPtr; + listRepPtr->elements[listRepPtr->elemCount] = objPtr; Tcl_IncrRefCount(objPtr); listRepPtr->elemCount++; @@ -817,7 +816,7 @@ Tcl_ListObjIndex( { List *listRepPtr; - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); if (listRepPtr == NULL) { int result; size_t length; @@ -831,13 +830,13 @@ Tcl_ListObjIndex( if (result != TCL_OK) { return result; } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); } if (index >= listRepPtr->elemCount) { *objPtrPtr = NULL; } else { - *objPtrPtr = (&listRepPtr->elements)[index]; + *objPtrPtr = listRepPtr->elements[index]; } return TCL_OK; @@ -871,11 +870,11 @@ int Tcl_ListObjLength( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *listPtr, /* List object whose #elements to return. */ - size_t *intPtr) /* The resulting size_t is stored here. */ + size_t *intPtr) /* The resulting length is stored here. */ { List *listRepPtr; - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); if (listRepPtr == NULL) { int result; size_t length; @@ -889,7 +888,7 @@ Tcl_ListObjLength( if (result != TCL_OK) { return result; } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); } *intPtr = listRepPtr->elemCount; @@ -954,7 +953,7 @@ Tcl_ListObjReplace( Tcl_Panic("%s called with shared object", "Tcl_ListObjReplace"); } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); if (listRepPtr == NULL) { size_t length; @@ -971,7 +970,7 @@ Tcl_ListObjReplace( return result; } } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); } /* @@ -982,7 +981,7 @@ Tcl_ListObjReplace( * Resist any temptation to optimize this case. */ - elemPtrs = &listRepPtr->elements; + elemPtrs = listRepPtr->elements; numElems = listRepPtr->elemCount; if (first == TCL_INDEX_NONE) { @@ -1027,8 +1026,8 @@ Tcl_ListObjReplace( } if (newPtr) { listRepPtr = newPtr; - ListResetIntRep(listPtr, listRepPtr); - elemPtrs = &listRepPtr->elements; + ListResetInternalRep(listPtr, listRepPtr); + elemPtrs = listRepPtr->elements; listRepPtr->maxElemCount = attempt; needGrow = numRequired > listRepPtr->maxElemCount; } @@ -1096,10 +1095,10 @@ Tcl_ListObjReplace( } } - ListResetIntRep(listPtr, listRepPtr); + ListResetInternalRep(listPtr, listRepPtr); listRepPtr->refCount++; - elemPtrs = &listRepPtr->elements; + elemPtrs = listRepPtr->elements; if (isShared) { /* @@ -1175,7 +1174,7 @@ Tcl_ListObjReplace( listRepPtr->refCount++; TclFreeInternalRep(listPtr); - ListSetIntRep(listPtr, listRepPtr); + ListSetInternalRep(listPtr, listRepPtr); listRepPtr->refCount--; TclInvalidateStringRep(listPtr); @@ -1221,7 +1220,7 @@ TclLindexList( * shimmering; see TIP#22 and TIP#33 for the details. */ - ListGetIntRep(argPtr, listRepPtr); + ListGetInternalRep(argPtr, listRepPtr); if ((listRepPtr == NULL) && TclGetIntForIndexM(NULL , argPtr, (size_t)WIDE_MAX - 1, &index) == TCL_OK) { /* @@ -1253,12 +1252,12 @@ TclLindexList( return TclLindexFlat(interp, listPtr, 1, &argPtr); } - ListGetIntRep(indexListCopy, listRepPtr); + ListGetInternalRep(indexListCopy, listRepPtr); assert(listRepPtr != NULL); listPtr = TclLindexFlat(interp, listPtr, listRepPtr->elemCount, - &listRepPtr->elements); + listRepPtr->elements); Tcl_DecrRefCount(indexListCopy); return listPtr; } @@ -1297,8 +1296,7 @@ TclLindexFlat( Tcl_IncrRefCount(listPtr); for (i=0 ; i<indexCount && listPtr ; i++) { - size_t index; - size_t listLen = 0; + size_t index, listLen = 0; Tcl_Obj **elemPtrs = NULL, *sublistCopy; /* @@ -1391,7 +1389,7 @@ TclLsetList( * shimmering; see TIP #22 and #23 for details. */ - ListGetIntRep(indexArgPtr, listRepPtr); + ListGetInternalRep(indexArgPtr, listRepPtr); if (listRepPtr == NULL && TclGetIntForIndexM(NULL, indexArgPtr, (size_t)WIDE_MAX - 1, &index) == TCL_OK) { /* @@ -1651,7 +1649,7 @@ TclLsetFlat( listRepPtr->refCount++; TclFreeInternalRep(objPtr); - ListSetIntRep(objPtr, listRepPtr); + ListSetInternalRep(objPtr, listRepPtr); listRepPtr->refCount--; TclInvalidateStringRep(objPtr); @@ -1756,7 +1754,7 @@ TclListObjSetElement( Tcl_Panic("%s called with shared object", "TclListObjSetElement"); } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); if (listRepPtr == NULL) { int result; size_t length; @@ -1775,7 +1773,7 @@ TclListObjSetElement( if (result != TCL_OK) { return result; } - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); } elemCount = listRepPtr->elemCount; @@ -1799,7 +1797,7 @@ TclListObjSetElement( */ if (listRepPtr->refCount > 1) { - Tcl_Obj **dst, **src = &listRepPtr->elements; + Tcl_Obj **dst, **src = listRepPtr->elements; List *newPtr = AttemptNewList(NULL, listRepPtr->maxElemCount, NULL); if (newPtr == NULL) { @@ -1812,7 +1810,7 @@ TclListObjSetElement( newPtr->elemCount = elemCount; newPtr->canonicalFlag = listRepPtr->canonicalFlag; - dst = &newPtr->elements; + dst = newPtr->elements; while (elemCount--) { *dst = *src++; Tcl_IncrRefCount(*dst++); @@ -1821,9 +1819,9 @@ TclListObjSetElement( listRepPtr->refCount--; listRepPtr = newPtr; - ListResetIntRep(listPtr, listRepPtr); + ListResetInternalRep(listPtr, listRepPtr); } - elemPtrs = &listRepPtr->elements; + elemPtrs = listRepPtr->elements; /* * Add a reference to the new list element. @@ -1847,10 +1845,10 @@ TclListObjSetElement( * Invalidate outdated internalreps. */ - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); listRepPtr->refCount++; TclFreeInternalRep(listPtr); - ListSetIntRep(listPtr, listRepPtr); + ListSetInternalRep(listPtr, listRepPtr); listRepPtr->refCount--; TclInvalidateStringRep(listPtr); @@ -1880,11 +1878,11 @@ FreeListInternalRep( { List *listRepPtr; - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); assert(listRepPtr != NULL); if (listRepPtr->refCount-- <= 1) { - Tcl_Obj **elemPtrs = &listRepPtr->elements; + Tcl_Obj **elemPtrs = listRepPtr->elements; int i, numElems = listRepPtr->elemCount; for (i = 0; i < numElems; i++) { @@ -1916,9 +1914,9 @@ DupListInternalRep( { List *listRepPtr; - ListGetIntRep(srcPtr, listRepPtr); + ListGetInternalRep(srcPtr, listRepPtr); assert(listRepPtr != NULL); - ListSetIntRep(copyPtr, listRepPtr); + ListSetInternalRep(copyPtr, listRepPtr); } /* @@ -1986,7 +1984,7 @@ SetListFromAny( * Populate the list representation. */ - elemPtrs = &listRepPtr->elements; + elemPtrs = listRepPtr->elements; Tcl_DictObjFirst(NULL, objPtr, &search, &keyPtr, &valuePtr, &done); while (!done) { *elemPtrs++ = keyPtr; @@ -1996,8 +1994,7 @@ SetListFromAny( Tcl_DictObjNext(&search, &keyPtr, &valuePtr, &done); } } else { - size_t estCount; - size_t length; + size_t estCount, length; const char *limit, *nextElem = Tcl_GetStringFromObj(objPtr, &length); /* @@ -2012,7 +2009,7 @@ SetListFromAny( if (listRepPtr == NULL) { return TCL_ERROR; } - elemPtrs = &listRepPtr->elements; + elemPtrs = listRepPtr->elements; /* * Each iteration, parse and store a list element. @@ -2027,7 +2024,7 @@ SetListFromAny( if (TCL_OK != TclFindElement(interp, nextElem, limit - nextElem, &elemStart, &nextElem, &elemSize, &literal)) { fail: - while (--elemPtrs >= &listRepPtr->elements) { + while (--elemPtrs >= listRepPtr->elements) { Tcl_DecrRefCount(*elemPtrs); } Tcl_Free(listRepPtr); @@ -2057,7 +2054,7 @@ SetListFromAny( Tcl_IncrRefCount(*elemPtrs++);/* Since list now holds ref to it. */ } - listRepPtr->elemCount = elemPtrs - &listRepPtr->elements; + listRepPtr->elemCount = elemPtrs - listRepPtr->elements; } /* @@ -2066,7 +2063,7 @@ SetListFromAny( * Tcl_GetStringFromObj, to use the old internalRep. */ - ListSetIntRep(objPtr, listRepPtr); + ListSetInternalRep(objPtr, listRepPtr); return TCL_OK; } @@ -2095,14 +2092,13 @@ UpdateStringOfList( { # define LOCAL_SIZE 64 char localFlags[LOCAL_SIZE], *flagPtr = NULL; - size_t numElems, i; - size_t length, bytesNeeded = 0; + size_t numElems, i, length, bytesNeeded = 0; const char *elem, *start; char *dst; Tcl_Obj **elemPtrs; List *listRepPtr; - ListGetIntRep(listPtr, listRepPtr); + ListGetInternalRep(listPtr, listRepPtr); assert(listRepPtr != NULL); @@ -2138,7 +2134,7 @@ UpdateStringOfList( flagPtr = (char *)Tcl_Alloc(numElems); } - elemPtrs = &listRepPtr->elements; + elemPtrs = listRepPtr->elements; for (i = 0; i < numElems; i++) { flagPtr[i] = (i ? TCL_DONT_QUOTE_HASH : 0); elem = Tcl_GetStringFromObj(elemPtrs[i], &length); diff --git a/generic/tclProc.c b/generic/tclProc.c index 0f70213..f8f3e12 100644 --- a/generic/tclProc.c +++ b/generic/tclProc.c @@ -1535,12 +1535,15 @@ TclPushProcCallFrame( * is up-to-date), the namespace must match (so variable handling * is right) and the resolverEpoch must match (so that new shadowed * commands and/or resolver changes are considered). + * Ensure the ByteCode's procPtr is the same (or it's precompiled). */ if (((Interp *) *codePtr->interpHandle != iPtr) || (codePtr->compileEpoch != iPtr->compileEpoch) || (codePtr->nsPtr != nsPtr) - || (codePtr->nsEpoch != nsPtr->resolverEpoch)) { + || (codePtr->nsEpoch != nsPtr->resolverEpoch) + || ((codePtr->procPtr != procPtr) && procPtr->bodyPtr->bytes) + ) { goto doCompilation; } } else { @@ -1881,6 +1884,7 @@ TclProcCompileProc( * procPtr->numCompiledLocals if new local variables are found while * compiling. * + * Ensure the ByteCode's procPtr is the same (or it is pure precompiled). * Precompiled procedure bodies, however, are immutable and therefore they * are not recompiled, even if things have changed. */ @@ -1889,7 +1893,9 @@ TclProcCompileProc( if (((Interp *) *codePtr->interpHandle == iPtr) && (codePtr->compileEpoch == iPtr->compileEpoch) && (codePtr->nsPtr == nsPtr) - && (codePtr->nsEpoch == nsPtr->resolverEpoch)) { + && (codePtr->nsEpoch == nsPtr->resolverEpoch) + && ((codePtr->procPtr == procPtr) || !bodyPtr->bytes) + ) { return TCL_OK; } @@ -2105,6 +2111,13 @@ TclProcCleanupProc( Interp *iPtr = procPtr->iPtr; if (bodyPtr != NULL) { + /* procPtr is stored in body's ByteCode, so ensure to reset it. */ + ByteCode *codePtr; + + ByteCodeGetInternalRep(bodyPtr, &tclByteCodeType, codePtr); + if (codePtr != NULL && codePtr->procPtr == procPtr) { + codePtr->procPtr = NULL; + } Tcl_DecrRefCount(bodyPtr); } for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; ) { diff --git a/generic/tclStrToD.c b/generic/tclStrToD.c index 9cd3811..a816062 100644 --- a/generic/tclStrToD.c +++ b/generic/tclStrToD.c @@ -49,44 +49,43 @@ * file exists only on Linux; it is missing on Cygwin and MinGW. Most gcc-isms * and ix86-isms are factored out here. */ - -#if defined(__GNUC__) +# if defined(__GNUC__) typedef unsigned int fpu_control_t __attribute__ ((__mode__ (__HI__))); -#define _FPU_GETCW(cw) __asm__ __volatile__ ("fnstcw %0" : "=m" (*&cw)) -#define _FPU_SETCW(cw) __asm__ __volatile__ ("fldcw %0" : : "m" (*&cw)) -# define FPU_IEEE_ROUNDING 0x027F -# define ADJUST_FPU_CONTROL_WORD -#define TCL_IEEE_DOUBLE_ROUNDING \ +# define _FPU_GETCW(cw) __asm__ __volatile__ ("fnstcw %0" : "=m" (*&cw)) +# define _FPU_SETCW(cw) __asm__ __volatile__ ("fldcw %0" : : "m" (*&cw)) +# define FPU_IEEE_ROUNDING 0x027F +# define ADJUST_FPU_CONTROL_WORD +# define TCL_IEEE_DOUBLE_ROUNDING_DECL \ fpu_control_t roundTo53Bits = FPU_IEEE_ROUNDING; \ - fpu_control_t oldRoundingMode; \ + fpu_control_t oldRoundingMode; +# define TCL_IEEE_DOUBLE_ROUNDING \ _FPU_GETCW(oldRoundingMode); \ _FPU_SETCW(roundTo53Bits) -#define TCL_DEFAULT_DOUBLE_ROUNDING \ +# define TCL_DEFAULT_DOUBLE_ROUNDING \ _FPU_SETCW(oldRoundingMode) /* * Sun ProC needs sunmath for rounding control on x86 like gcc above. */ -#elif defined(__sun) -#include <sunmath.h> -#define TCL_IEEE_DOUBLE_ROUNDING \ +# elif defined(__sun) +# include <sunmath.h> +# define TCL_IEEE_DOUBLE_ROUNDING_DECL +# define TCL_IEEE_DOUBLE_ROUNDING \ ieee_flags("set","precision","double",NULL) -#define TCL_DEFAULT_DOUBLE_ROUNDING \ +# define TCL_DEFAULT_DOUBLE_ROUNDING \ ieee_flags("clear","precision",NULL,NULL) +# endif +#endif /* * Other platforms are assumed to always operate in full IEEE mode, so we make * the macros to go in and out of that mode do nothing. */ - -#else /* !__GNUC__ && !__sun */ -#define TCL_IEEE_DOUBLE_ROUNDING ((void) 0) -#define TCL_DEFAULT_DOUBLE_ROUNDING ((void) 0) -#endif -#else /* !__i386 */ -#define TCL_IEEE_DOUBLE_ROUNDING ((void) 0) -#define TCL_DEFAULT_DOUBLE_ROUNDING ((void) 0) +#ifndef TCL_IEEE_DOUBLE_ROUNDING /* !__i386 || (!__GNUC__ && !__sun) */ +# define TCL_IEEE_DOUBLE_ROUNDING_DECL +# define TCL_IEEE_DOUBLE_ROUNDING ((void) 0) +# define TCL_DEFAULT_DOUBLE_ROUNDING ((void) 0) #endif /* @@ -1210,7 +1209,6 @@ TclParseNumber( acceptPoint = p; acceptLen = len; goto endgame; - } p++; len--; @@ -1679,7 +1677,8 @@ MakeLowPrecisionDouble( int numSigDigs, /* Number of digits in the significand */ long exponent) /* Power of ten */ { - double retval; /* Value of the number. */ + TCL_IEEE_DOUBLE_ROUNDING_DECL + mp_int significandBig; /* Significand expressed as a bignum. */ /* @@ -1687,18 +1686,25 @@ MakeLowPrecisionDouble( * This causes the result of double-precision calculations to be rounded * twice: once to the precision of double-extended and then again to the * precision of double. Double-rounding introduces gratuitous errors of 1 - * ulp, so we need to change rounding mode to 53-bits. + * ulp, so we need to change rounding mode to 53-bits. We also make + * 'retval' volatile, so that it doesn't get promoted to a register. */ - - TCL_IEEE_DOUBLE_ROUNDING; + volatile double retval; /* Value of the number. */ /* - * Test for the easy cases. + * Test for zero significand, which requires explicit construction + * of -0.0. (Unary minus returns a positive zero.) */ - if (significand == 0) { return copysign(0.0, -signum); } + + /* + * Set the FP control word for 53 bits, WARNING: It must be reset + * before returning. + */ + TCL_IEEE_DOUBLE_ROUNDING; + if (numSigDigs <= QUICK_MAX) { if (exponent >= 0) { if (exponent <= mmaxpow) { @@ -1798,7 +1804,8 @@ MakeHighPrecisionDouble( int numSigDigs, /* Number of significant digits */ long exponent) /* Power of 10 by which to multiply */ { - double retval; + TCL_IEEE_DOUBLE_ROUNDING_DECL + int machexp = 0; /* Machine exponent of a power of 10. */ /* @@ -1806,19 +1813,30 @@ MakeHighPrecisionDouble( * This causes the result of double-precision calculations to be rounded * twice: once to the precision of double-extended and then again to the * precision of double. Double-rounding introduces gratuitous errors of 1 - * ulp, so we need to change rounding mode to 53-bits. + * ulp, so we need to change rounding mode to 53-bits. We also make + * 'retval' volatile to make sure that it doesn't get promoted to a + * register. */ - - TCL_IEEE_DOUBLE_ROUNDING; + volatile double retval; /* - * Quick checks for zero, and over/underflow. Be careful to avoid - * integer overflow when calculating with 'exponent'. + * A zero significand requires explicit construction of -0.0. + * (Unary minus returns positive zero.) */ - if (mp_iszero(significand)) { return copysign(0.0, -signum); } + + /* + * Set the 53-bit rounding mode. WARNING: It must be reset before + * returning. + */ + TCL_IEEE_DOUBLE_ROUNDING; + + /* + * Make quick checks for over/underflow. Be careful to avoid + * integer overflow when calculating with 'exponent'. + */ if (exponent >= 0 && exponent-1 > maxDigits-numSigDigs) { retval = HUGE_VAL; goto returnValue; diff --git a/generic/tclTest.c b/generic/tclTest.c index 509b97a..b961b6f 100644 --- a/generic/tclTest.c +++ b/generic/tclTest.c @@ -335,6 +335,7 @@ static Tcl_ObjCmdProc TestInterpResolverCmd; #if defined(HAVE_CPUID) && !defined(MAC_OSX_TCL) static Tcl_ObjCmdProc TestcpuidCmd; #endif +static Tcl_ObjCmdProc TestApplyLambdaObjCmd; static const Tcl_Filesystem testReportingFilesystem = { "reporting", @@ -709,6 +710,8 @@ Tcltest_Init( NULL, NULL); Tcl_CreateObjCommand(interp, "testsetencpath", TestsetencpathObjCmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "testapplylambda", TestApplyLambdaObjCmd, + NULL, NULL); if (TclObjTest_Init(interp) != TCL_OK) { return TCL_ERROR; @@ -8085,7 +8088,85 @@ TestInterpResolverCmd( } return TCL_OK; } - + +/* + *------------------------------------------------------------------------ + * + * TestApplyLambdaObjCmd -- + * + * Implements the Tcl command testapplylambda. This tests the apply + * implementation handling of a lambda where the lambda has a list + * internal representation where the second element's internal + * representation is already a byte code object. + * + * Results: + * TCL_OK - Success. Caller should check result is 42 + * TCL_ERROR - Error. + * + * Side effects: + * In the presence of the apply bug, may panic. Otherwise + * Interpreter result holds result or error message. + * + *------------------------------------------------------------------------ + */ +int TestApplyLambdaObjCmd ( + TCL_UNUSED(void*), + Tcl_Interp *interp, /* Current interpreter. */ + TCL_UNUSED(int), /* objc. */ + TCL_UNUSED(Tcl_Obj *const *)) /* objv. */ +{ + Tcl_Obj *lambdaObjs[2]; + Tcl_Obj *evalObjs[2]; + Tcl_Obj *lambdaObj; + int result; + + /* Create a lambda {{} {set a 42}} */ + lambdaObjs[0] = Tcl_NewObj(); /* No parameters */ + lambdaObjs[1] = Tcl_NewStringObj("set a 42", -1); /* Body */ + lambdaObj = Tcl_NewListObj(2, lambdaObjs); + Tcl_IncrRefCount(lambdaObj); + + /* Create the command "apply {{} {set a 42}" */ + evalObjs[0] = Tcl_NewStringObj("apply", -1); + Tcl_IncrRefCount(evalObjs[0]); + /* + * NOTE: IMPORTANT TO EXHIBIT THE BUG. We duplicate the lambda because + * it will get shimmered to a Lambda internal representation but we + * want to hold on to our list representation. + */ + evalObjs[1] = Tcl_DuplicateObj(lambdaObj); + Tcl_IncrRefCount(evalObjs[1]); + + /* Evaluate it */ + result = Tcl_EvalObjv(interp, 2, evalObjs, TCL_EVAL_GLOBAL); + if (result != TCL_OK) { + Tcl_DecrRefCount(evalObjs[0]); + Tcl_DecrRefCount(evalObjs[1]); + return result; + } + /* + * So far so good. At this point, + * - evalObjs[1] has an internal representation of Lambda + * - lambdaObj[1] ({set a 42}) has been shimmered to + * an internal representation of ByteCode. + */ + Tcl_DecrRefCount(evalObjs[1]); /* Don't need this anymore */ + /* + * The bug trigger. Repeating the command but: + * - we are calling apply with a lambda that is a list (as BEFORE), + * BUT + * - The body of the lambda (lambdaObjs[1]) ALREADY has internal + * representation of ByteCode and thus will not be compiled again + */ + evalObjs[1] = lambdaObj; /* lambdaObj already has a ref count so + no need for IncrRef */ + result = Tcl_EvalObjv(interp, 2, evalObjs, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(evalObjs[0]); + Tcl_DecrRefCount(lambdaObj); + + return result; +} + /* * Local Variables: * mode: c @@ -8095,3 +8176,4 @@ TestInterpResolverCmd( * indent-tabs-mode: nil * End: */ + diff --git a/generic/tclUtf.c b/generic/tclUtf.c index 532abab..e882f18 100644 --- a/generic/tclUtf.c +++ b/generic/tclUtf.c @@ -508,8 +508,7 @@ Tcl_UtfToUniChar( * A three-byte-character lead-byte not followed by two trail-bytes * represents itself. */ - } - else if (byte < 0xF5) { + } else if (byte < 0xF5) { if (((src[1] & 0xC0) == 0x80) && ((src[2] & 0xC0) == 0x80) && ((src[3] & 0xC0) == 0x80)) { /* * Four-byte-character lead byte followed by three trail bytes. @@ -604,8 +603,7 @@ Tcl_UtfToChar16( * A three-byte-character lead-byte not followed by two trail-bytes * represents itself. */ - } - else if (byte < 0xF5) { + } else if (byte < 0xF5) { if (((src[1] & 0xC0) == 0x80) && ((src[2] & 0xC0) == 0x80)) { /* * Four-byte-character lead byte followed by at least two trail bytes. diff --git a/generic/tclZipfs.c b/generic/tclZipfs.c index cd31a19..008e10b 100644 --- a/generic/tclZipfs.c +++ b/generic/tclZipfs.c @@ -410,7 +410,7 @@ static const Tcl_Filesystem zipfsFilesystem = { static Tcl_ChannelType ZipChannelType = { "zip", /* Type name. */ TCL_CHANNEL_VERSION_5, - TCL_CLOSE2PROC, /* Close channel, clean instance data */ + NULL, /* Close channel, clean instance data */ ZipChannelRead, /* Handle read request */ ZipChannelWrite, /* Handle write request */ NULL, /* Move location of access point, NULL'able */ diff --git a/library/manifest.txt b/library/manifest.txt index 6b70b24..b425920 100644 --- a/library/manifest.txt +++ b/library/manifest.txt @@ -12,7 +12,7 @@ apply {{dir} { 0 tcl::idna 1.0.1 {cookiejar idna.tcl} 0 platform 1.0.18 {platform platform.tcl} 0 platform::shell 1.1.4 {platform shell.tcl} - 1 tcltest 2.5.4 {tcltest tcltest.tcl} + 1 tcltest 2.5.5 {tcltest tcltest.tcl} } { if {$isafe && !$safe} continue package ifneeded $package $version [list source [file join $dir {*}$file]] diff --git a/library/tcltest/pkgIndex.tcl b/library/tcltest/pkgIndex.tcl index da78df0..18b05e5 100644 --- a/library/tcltest/pkgIndex.tcl +++ b/library/tcltest/pkgIndex.tcl @@ -9,4 +9,4 @@ # full path name of this file's directory. if {![package vsatisfies [package provide Tcl] 8.5-]} {return} -package ifneeded tcltest 2.5.4 [list source [file join $dir tcltest.tcl]] +package ifneeded tcltest 2.5.5 [list source [file join $dir tcltest.tcl]] diff --git a/library/tcltest/tcltest.tcl b/library/tcltest/tcltest.tcl index 72c7b94..7344f9f 100644 --- a/library/tcltest/tcltest.tcl +++ b/library/tcltest/tcltest.tcl @@ -22,7 +22,7 @@ namespace eval tcltest { # When the version number changes, be sure to update the pkgIndex.tcl file, # and the install directory in the Makefiles. When the minor version # changes (new feature) be sure to update the man page as well. - variable Version 2.5.4 + variable Version 2.5.5 # Compatibility support for dumb variables defined in tcltest 1 # Do not use these. Call [package provide Tcl] and [info patchlevel] @@ -2141,7 +2141,7 @@ proc tcltest::test {name description args} { if {[IsVerbose msec] || [IsVerbose usec]} { set t [expr {[clock microseconds] - $timeStart}] if {[IsVerbose usec]} { - puts [outputChannel] "++++ $name took $t μs" + puts [outputChannel] "++++ $name took $t \xB5s" } if {[IsVerbose msec]} { puts [outputChannel] "++++ $name took [expr {round($t/1000.)}] ms" diff --git a/macosx/tclMacOSXFCmd.c b/macosx/tclMacOSXFCmd.c index 5030b2f..020288f 100644 --- a/macosx/tclMacOSXFCmd.c +++ b/macosx/tclMacOSXFCmd.c @@ -344,8 +344,8 @@ TclMacOSXSetFileAttribute( */ Tcl_DStringInit(&ds); - Tcl_DStringAppend(&ds, native, -1); - Tcl_DStringAppend(&ds, _PATH_RSRCFORKSPEC, -1); + Tcl_DStringAppend(&ds, native, TCL_INDEX_NONE); + Tcl_DStringAppend(&ds, _PATH_RSRCFORKSPEC, TCL_INDEX_NONE); result = truncate(Tcl_DStringValue(&ds), 0); if (result != 0) { @@ -459,11 +459,11 @@ TclMacOSXCopyFileAttributes( */ Tcl_DStringInit(&srcBuf); - Tcl_DStringAppend(&srcBuf, src, -1); - Tcl_DStringAppend(&srcBuf, _PATH_RSRCFORKSPEC, -1); + Tcl_DStringAppend(&srcBuf, src, TCL_INDEX_NONE); + Tcl_DStringAppend(&srcBuf, _PATH_RSRCFORKSPEC, TCL_INDEX_NONE); Tcl_DStringInit(&dstBuf); - Tcl_DStringAppend(&dstBuf, dst, -1); - Tcl_DStringAppend(&dstBuf, _PATH_RSRCFORKSPEC, -1); + Tcl_DStringAppend(&dstBuf, dst, TCL_INDEX_NONE); + Tcl_DStringAppend(&dstBuf, _PATH_RSRCFORKSPEC, TCL_INDEX_NONE); /* * Do the copy. diff --git a/tests/apply.test b/tests/apply.test index e2be172..a5f1f8f 100644 --- a/tests/apply.test +++ b/tests/apply.test @@ -16,12 +16,16 @@ if {"::tcltest" ni [namespace children]} { package require tcltest 2.5 namespace import -force ::tcltest::* } +::tcltest::loadTestedCommands +catch [list package require -exact tcl::test [info patchlevel]] if {[info commands ::apply] eq {}} { return } testConstraint memory [llength [info commands memory]] +testConstraint applylambda [llength [info commands testapplylambda]] + # Tests for wrong number of arguments @@ -306,6 +310,13 @@ test apply-9.3 {leaking internal rep} -setup { unset -nocomplain end i x tmp leakedBytes } -result 0 +# Tests for specific bugs +test apply-10.1 {Test for precompiled bytecode body} -constraints { + applylambda +} -body { + testapplylambda +} -result 42 + # Tests for the avoidance of recompilation # cleanup diff --git a/tests/io.test b/tests/io.test index f07fa8d..6314ace 100644 --- a/tests/io.test +++ b/tests/io.test @@ -336,6 +336,15 @@ test io-3.8 {WriteChars: reset sawLF after each buffer} { close $f lappend x [contents $path(test1)] } [list "abcdefg\nhijklmno" "abcdefg\nhijklmnopqrstuvwxyz"] +test io-3.9 {Write: flush line-buffered channels when crlf is split over two buffers} -body { + # https://core.tcl-lang.org/tcllib/tktedit?name=c9d8a52fe + set f [open $path(test1) w] + fconfigure $f -buffering line -translation crlf -buffersize 8 + puts $f "1234567" + string map {"\r" "<cr>" "\n" "<lf>"} [contents $path(test1)] +} -cleanup { + close $f +} -result "1234567<cr><lf>" test io-4.1 {TranslateOutputEOL: lf} { # search for \n @@ -3067,6 +3076,99 @@ test io-29.35 {Tcl_Close vs fileevent vs multiple interpreters} {socket tempNotM interp delete y } "" +test io-29.36.1 {gets on translation auto with "\r" in QA communication mode, possible regression, bug [b3977d199b]} -constraints { + socket tempNotMac fileevent +} -setup { + set s [open "|[list [interpreter] << { + proc accept {so args} { + fconfigure $so -translation binary + puts -nonewline $so "who are you?\r"; flush $so + set a [gets $so] + puts -nonewline $so "really $a?\r"; flush $so + set a [gets $so] + close $so + set ::done $a + } + set s [socket -server [namespace code accept] -myaddr 127.0.0.1 0] + puts [lindex [fconfigure $s -sockname] 2] + foreach c {1 2} { + vwait ::done + puts $::done + } + }]" r] + set c {} + set result {} +} -body { + set port [gets $s] + foreach t {{cr lf} {auto lf}} { + set c [socket 127.0.0.1 $port] + fconfigure $c -buffering line -translation $t + lappend result $t + while {1} { + set q [gets $c] + switch -- $q { + "who are you?" {puts $c "client"} + "really client?" {puts $c "yes"; lappend result $q; break} + default {puts $c "wrong"; lappend result "unexpected input \"$q\""; break} + } + } + lappend result [gets $s] + close $c; set c {} + } + set result +} -cleanup { + close $s + if {$c ne {}} { close $c } + unset -nocomplain s c port t q +} -result [list {cr lf} "really client?" yes {auto lf} "really client?" yes] +test io-29.36.2 {gets on translation auto with "\r\n" in different buffers, bug [b3977d199b]} -constraints { + socket tempNotMac fileevent +} -setup { + set s [socket -server [namespace code accept] -myaddr 127.0.0.1 0] + set c {} +} -body { + set ::cnt 0 + proc accept {so args} { + fconfigure $so -translation binary + puts -nonewline $so "1 line\r" + puts -nonewline $so "\n2 li" + flush $so + # now force separate packets + puts -nonewline $so "ne\r" + flush $so + if {$::cnt & 1} { + vwait ::cli; # simulate short delay (so client can process events, just wait for it) + } else { + # we don't have a delay, so client would get the lines as single chunk + } + # we'll try with "\r" and without "\r" (to cover both branches, where "\r" and "eof" causes exit from [gets] by 3rd line) + puts -nonewline $so "\n3 line" + if {!($::cnt % 3)} { + puts -nonewline $so "\r" + } + flush $so + close $so + } + while {$::cnt < 6} { incr ::cnt + set c [socket 127.0.0.1 [lindex [fconfigure $s -sockname] 2]] + fconfigure $c -blocking 0 -buffering line -translation auto + fileevent $c readable [list apply {c { + if {[gets $c line] >= 0} { + lappend ::cli <$line> + } elseif {[eof $c]} { + set ::done 1 + } + }} $c] + vwait ::done + close $c; set c {} + } + set ::cli +} -cleanup { + close $s + if {$c ne {}} { close $c } + unset -nocomplain ::done ::cli ::cnt s c +} -result [lrepeat 6 {<1 line>} {<2 line>} {<3 line>}] + # Test end of line translations. Procedures tested are Tcl_Write, Tcl_Read. test io-30.1 {Tcl_Write lf, Tcl_Read lf} { diff --git a/tests/winConsole.test b/tests/winConsole.test index 8ca1457..821a143 100644 --- a/tests/winConsole.test +++ b/tests/winConsole.test @@ -14,34 +14,361 @@ if {"::tcltest" ni [namespace children]} { namespace import -force ::tcltest::* } +catch {package require twapi} ;# Only to bring window to foreground. Not critical -test winConsole-1.1 {Console file channel: non-blocking gets} {win interactive} { - set oldmode [fconfigure stdin] +::tcltest::ConstraintInitializer haveThread { expr {![catch {package require Thread}]} } + +# Prompt user for a yes/no response +proc yesno {question {default "Y"}} { + set answer "" + # Make sure we are seen but catch because ui and console + # packages may not be available + catch {twapi::set_foreground_window [twapi::get_console_window]} + while {![string is boolean -strict $answer]} { + puts -nonewline stdout "$question Type Y/N followed by Enter \[$default\] : " + flush stdout + set answer [string trim [gets stdin]] + if {$answer eq ""} { + set answer $default + } + } + return [expr {!! $answer}] +} - puts stdout "Enter abcdef<return> now: " nonewline +proc prompt {prompt} { + # Make sure we are seen but catch because twapi ui and console + # packages may not be available + catch {twapi::set_foreground_window [twapi::get_console_window]} + puts -nonewline stdout "$prompt" flush stdout +} + +# Input tests + +test console-input-1.0 {Console blocking gets} -constraints {win interactive} -body { + prompt "Type \"xyz\" and hit Enter: " + gets stdin +} -result xyz + +test console-input-1.1 {Console file channel: non-blocking gets} -constraints { + win interactive +} -setup { + unset -nocomplain result + unset -nocomplain result2 +} -body { + set oldmode [fconfigure stdin] + + prompt "Type \"abc\" and hit Enter: " fileevent stdin readable { if {[gets stdin line] >= 0} { - set result $line - } else { + lappend result2 $line + if {[llength $result2] > 1} { + set result $result2 + } else { + prompt "Type \"def\" and hit Enter: " + } + } elseif {[eof stdin]} { set result "gets failed" } } fconfigure stdin -blocking 0 -buffering line - set result {} vwait result #cleanup the fileevent fileevent stdin readable {} fconfigure stdin {*}$oldmode + set result + +} -result {abc def} + +test console-input-1.1.1 {Bug baa51423c28a: Console file channel: fileevent with blocking gets} -constraints { + win interactive +} -setup { + unset -nocomplain result + unset -nocomplain result2 +} -body { + prompt "Type \"abc\" and hit Enter: " + fileevent stdin readable { + if {[gets stdin line] >= 0} { + lappend result2 $line + if {[llength $result2] > 1} { + set result $result2 + } else { + prompt "Type \"def\" and hit Enter: " + } + } elseif {[eof stdin]} { + set result "gets failed" + } + } + + vwait result + #cleanup the fileevent + fileevent stdin readable {} + set result + +} -result {abc def} + +test console-input-2.0 {Console blocking read} -constraints {win interactive} -setup { + set oldmode [fconfigure stdin] + fconfigure stdin -inputmode raw +} -cleanup { + fconfigure stdin {*}$oldmode +} -body { + prompt "Type the key \"a\". Do NOT hit Enter. You will NOT see characters echoed." + set c [read stdin 1] + puts "" + set c +} -result a + +test console-input-2.1 {Console file channel: non-blocking read} -constraints { + win interactive +} -setup { + set oldmode [fconfigure stdin] +} -cleanup { + fconfigure stdin {*}$oldmode + puts ""; # Because CRLF also would not have been echoed +} -body { + set input "" + fconfigure stdin -blocking 0 -buffering line -inputmode raw + prompt "Type \"abc\". Do NOT hit Enter. You will NOT see characters echoed." + + fileevent stdin readable { + set c [read stdin 1] + if {$c eq ""} { + if {[eof stdin]} { + set result "read eof" + } + } else { + append input $c + if {[string length $input] == 3} { + set result $input + } + } + } + + set result {} + vwait result + fileevent stdin readable {} set result +} -result abc + +# Output tests + +test console-output-1.0 {Console blocking puts stdout} -constraints {win interactive} -body { + puts stdout "123" + yesno "Did you see the string \"123\"?" +} -result 1 + +test console-output-1.1 {Console non-blocking puts stdout} -constraints { + win interactive +} -setup { + set oldmode [fconfigure stdout] + dict unset oldmode -winsize +} -cleanup { + fconfigure stdout {*}$oldmode +} -body { + fconfigure stdout -blocking 0 -buffering line + set count 0 + fileevent stdout writable { + if {[incr count] < 4} { + puts "$count" + } else { + fileevent stdout writable {} + set done 1 + } + } + vwait done + yesno "Did you see 1, 2, 3 printed on consecutive lines?" +} -result 1 + +test console-output-2.0 {Console blocking puts stderr} -constraints {win interactive} -body { + puts stderr "456" + yesno "Did you see the string \"456\"?" +} -result 1 + + +# fconfigure get tests + +## fconfigure get stdin + +test console-fconfigure-get-1.0 { + Console get stdin configuration +} -constraints {win interactive} -body { + lsort [dict keys [fconfigure stdin]] +} -result {-blocking -buffering -buffersize -encoding -eofchar -inputmode -translation} + +set testnum 0 +foreach {opt result} { + -blocking 1 + -buffering line + -buffersize 4096 + -encoding utf-16 + -inputmode normal + -translation auto +} { + test console-fconfigure-get-1.[incr testnum] "Console get stdin option $opt" \ + -constraints {win interactive} -body { + fconfigure stdin $opt + } -result $result +} +test console-fconfigure-get-1.[incr testnum] { + Console get stdin option -eofchar +} -constraints {win interactive} -body { + fconfigure stdin -eofchar +} -result \x1a + +test console-fconfigure-get-1.[incr testnum] { + fconfigure -winsize +} -constraints {win interactive} -body { + fconfigure stdin -winsize +} -result {bad option "-winsize": should be one of -blocking, -buffering, -buffersize, -encoding, -eofchar, -translation, or -inputmode} -returnCodes error + +## fconfigure get stdout/stderr +foreach chan {stdout stderr} major {2 3} { + test console-fconfigure-get-$major.0 "Console get $chan configuration" -constraints { + win interactive + } -body { + lsort [dict keys [fconfigure $chan]] + } -result {-blocking -buffering -buffersize -encoding -eofchar -translation -winsize} + set testnum 0 + foreach {opt result} { + -blocking 1 + -buffersize 4096 + -encoding utf-16 + -translation crlf + } { + test console-fconfigure-get-$major.[incr testnum] "Console get $chan option $opt" \ + -constraints {win interactive} -body { + fconfigure $chan $opt + } -result $result + } + + test console-fconfigure-get-$major.[incr testnum] "Console get $chan option -winsize" \ + -constraints {win interactive} -body { + fconfigure $chan -winsize + } -result {\d+ \d+} -match regexp + + test console-fconfigure-get-$major.[incr testnum] "Console get $chan option -buffering" \ + -constraints {win interactive} -body { + fconfigure $chan -buffering + } -result [expr {$chan eq "stdout" ? "line" : "none"}] + + test console-fconfigure-get-$major.[incr testnum] { + fconfigure -inputmode + } -constraints {win interactive} -body { + fconfigure $chan -inputmode + } -result {bad option "-inputmode": should be one of -blocking, -buffering, -buffersize, -encoding, -eofchar, -translation, or -winsize} -returnCodes error + +} + +## fconfigure set stdin + +test console-fconfigure-set-1.0 { + fconfigure -inputmode password +} -constraints {win interactive} -body { + set result {} + + prompt "Type \"pass\" and hit Enter. You should NOT see characters echoed: " + fconfigure stdin -inputmode password + lappend result [gets stdin] + lappend result [fconfigure stdin -inputmode] + fconfigure stdin -inputmode normal + lappend result [yesno "\nWere the characters echoed?"] + + prompt "Type \"norm\" and hit Enter. You should see characters echoed: " + lappend result [gets stdin] + lappend result [fconfigure stdin -inputmode] + lappend result [yesno "Were the characters echoed?"] + + set result +} -result [list pass password 0 norm normal 1] + +test console-fconfigure-set-1.1 { + fconfigure -inputmode raw +} -constraints {win interactive} -body { + set result {} + + prompt "Type the keys \"a\", Ctrl-H, \"b\". Do NOT hit Enter. You should NOT see characters echoed: " + fconfigure stdin -inputmode raw + lappend result [read stdin 3] + lappend result [fconfigure stdin -inputmode] + fconfigure stdin -inputmode normal + lappend result [yesno "\nWere the characters echoed?"] + + prompt "Type the keys \"c\", Ctrl-H, \"d\" and hit Enter. You should see characters echoed: " + lappend result [gets stdin] + lappend result [fconfigure stdin -inputmode] + lappend result [yesno "Were the characters echoed (c replaced by d)?"] + + set result +} -result [list a\x08b raw 0 d normal 1] + +test console-fconfigure-set-1.2 { + fconfigure -inputmode reset +} -constraints {win interactive} -body { + set result {} + + prompt "Type \"pass\" and hit Enter. You should NOT see characters echoed: " + fconfigure stdin -inputmode password + lappend result [gets stdin] + lappend result [fconfigure stdin -inputmode] + fconfigure stdin -inputmode reset + lappend result [yesno "\nWere the characters echoed?"] + + prompt "Type \"reset\" and hit Enter. You should see characters echoed: " + lappend result [gets stdin] + lappend result [fconfigure stdin -inputmode] + lappend result [yesno "Were the characters echoed?"] + + set result +} -result [list pass password 0 reset normal 1] + +test console-fconfigure-set-1.3 { + fconfigure stdin -winsize +} -constraints {win interactive} -body { + fconfigure stdin -winsize {10 30} +} -result {bad option "-winsize": should be one of -blocking, -buffering, -buffersize, -encoding, -eofchar, -translation, or -inputmode} -returnCodes error + +## fconfigure set stdout,stderr + +test console-fconfigure-set-2.0 { + fconfigure stdout -winsize +} -constraints {win interactive} -body { + fconfigure stdout -winsize {10 30} +} -result {bad option "-winsize": should be one of -blocking, -buffering, -buffersize, -encoding, -eofchar, or -translation} -returnCodes error + +test console-fconfigure-set-3.0 { + fconfigure stderr -winsize +} -constraints {win interactive} -body { + fconfigure stderr -winsize {10 30} +} -result {bad option "-winsize": should be one of -blocking, -buffering, -buffersize, -encoding, -eofchar, or -translation} -returnCodes error + +# Multiple threads -} "abcdef" +test console-thread-input-1.0 {Get input in thread} -constraints { + win interactive haveThread +} -setup { + set tid [thread::create] +} -cleanup { + thread::release $tid +} -body { + prompt "Type \"xyz\" and hit Enter: " + thread::send $tid {gets stdin} +} -result xyz -#cleanup +test console-thread-output-1.0 {Output from thread} -constraints { + win interactive haveThread +} -setup { + set tid [thread::create] +} -cleanup { + thread::release $tid +} -body { + thread::send $tid {puts [thread::id]} + yesno "Did you see $tid printed?" +} -result 1 ::tcltest::cleanupTests return diff --git a/unix/Makefile.in b/unix/Makefile.in index b43380a..2f44045 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -176,7 +176,7 @@ NATIVE_TCLSH = @TCLSH_PROG@ STLIB_LD = @STLIB_LD@ SHLIB_LD = @SHLIB_LD@ -SHLIB_CFLAGS = @SHLIB_CFLAGS@ -DBUILD_tcl +SHLIB_CFLAGS = @SHLIB_CFLAGS@ SHLIB_LD_LIBS = @SHLIB_LD_LIBS@ SHLIB_LD_FLAGS = @SHLIB_LD_FLAGS@ TCL_SHLIB_LD_EXTRAS = @TCL_SHLIB_LD_EXTRAS@ @@ -278,12 +278,12 @@ VALGRINDARGS = --tool=memcheck --num-callers=24 \ STUB_CC_SWITCHES = -I"${BUILD_DIR}" -I${UNIX_DIR} -I${GENERIC_DIR} -I${TOMMATH_DIR} \ ${CFLAGS} ${CFLAGS_WARNING} ${SHLIB_CFLAGS} \ - ${AC_FLAGS} ${ENV_FLAGS} ${EXTRA_CFLAGS} \ - @EXTRA_CC_SWITCHES@ + ${AC_FLAGS} ${ENV_FLAGS} ${EXTRA_CFLAGS} @EXTRA_CC_SWITCHES@ \ + ${NO_DEPRECATED_FLAGS} -DMP_FIXED_CUTOFFS -DMP_NO_STDINT -CC_SWITCHES = $(STUB_CC_SWITCHES) ${NO_DEPRECATED_FLAGS} -DMP_FIXED_CUTOFFS -DMP_NO_STDINT +CC_SWITCHES = $(STUB_CC_SWITCHES) -DBUILD_tcl -APP_CC_SWITCHES = $(CC_SWITCHES) @EXTRA_APP_CC_SWITCHES@ +APP_CC_SWITCHES = $(STUB_CC_SWITCHES) @EXTRA_APP_CC_SWITCHES@ LIBS = @TCL_LIBS@ @@ -1053,9 +1053,9 @@ install-libraries: libraries @echo "Installing package msgcat 1.7.1 as a Tcl Module" @$(INSTALL_DATA) $(TOP_DIR)/library/msgcat/msgcat.tcl \ "$(MODULE_INSTALL_DIR)/9.0/msgcat-1.7.1.tm" - @echo "Installing package tcltest 2.5.4 as a Tcl Module" + @echo "Installing package tcltest 2.5.5 as a Tcl Module" @$(INSTALL_DATA) $(TOP_DIR)/library/tcltest/tcltest.tcl \ - "$(MODULE_INSTALL_DIR)/9.0/tcltest-2.5.4.tm" + "$(MODULE_INSTALL_DIR)/9.0/tcltest-2.5.5.tm" @echo "Installing package platform 1.0.18 as a Tcl Module" @$(INSTALL_DATA) $(TOP_DIR)/library/platform/platform.tcl \ "$(MODULE_INSTALL_DIR)/9.0/platform-1.0.18.tm" diff --git a/unix/tclAppInit.c b/unix/tclAppInit.c index 1fcccd8..05d25de 100644 --- a/unix/tclAppInit.c +++ b/unix/tclAppInit.c @@ -13,9 +13,14 @@ */ #include "tcl.h" -#if TCL_MAJOR_VERSION < 9 && TCL_MINOR_VERSION < 7 +#if TCL_MAJOR_VERSION < 9 +# if defined(USE_TCL_STUBS) +# error "Don't build with USE_TCL_STUBS!" +# endif +# if TCL_MINOR_VERSION < 7 # define Tcl_LibraryInitProc Tcl_PackageInitProc # define Tcl_StaticLibrary Tcl_StaticPackage +# endif #endif #ifdef TCL_TEST @@ -86,7 +91,7 @@ main( TclZipfs_AppHook(&argc, &argv); #endif - Tcl_Main(argc, argv, TCL_LOCAL_APPINIT); + Tcl_Main((size_t)argc, argv, TCL_LOCAL_APPINIT); return 0; /* Needed only to prevent compiler warning. */ } @@ -113,7 +118,7 @@ int Tcl_AppInit( Tcl_Interp *interp) /* Interpreter for application. */ { - if ((Tcl_Init)(interp) == TCL_ERROR) { + if (Tcl_Init(interp) == TCL_ERROR) { return TCL_ERROR; } @@ -155,11 +160,11 @@ Tcl_AppInit( */ #ifdef DJGPP - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("tcl_rcFileName", -1), NULL, - Tcl_NewStringObj("~/tclsh.rc", -1), TCL_GLOBAL_ONLY); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("tcl_rcFileName", TCL_INDEX_NONE), NULL, + Tcl_NewStringObj("~/tclsh.rc", TCL_INDEX_NONE), TCL_GLOBAL_ONLY); #else - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("tcl_rcFileName", -1), NULL, - Tcl_NewStringObj("~/.tclshrc", -1), TCL_GLOBAL_ONLY); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("tcl_rcFileName", TCL_INDEX_NONE), NULL, + Tcl_NewStringObj("~/.tclshrc", TCL_INDEX_NONE), TCL_GLOBAL_ONLY); #endif return TCL_OK; diff --git a/unix/tclLoadDl.c b/unix/tclLoadDl.c index bd3e92c..dd6c50e 100644 --- a/unix/tclLoadDl.c +++ b/unix/tclLoadDl.c @@ -108,7 +108,7 @@ TclpDlopen( Tcl_DString ds; const char *fileName = TclGetString(pathPtr); - native = Tcl_UtfToExternalDString(NULL, fileName, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, fileName, TCL_INDEX_NONE, &ds); /* * Use (RTLD_NOW|RTLD_LOCAL) as default, see [Bug #3216070] */ @@ -179,12 +179,12 @@ FindSymbol( * the underscore. */ - native = Tcl_UtfToExternalDString(NULL, symbol, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, symbol, TCL_INDEX_NONE, &ds); proc = dlsym(handle, native); /* INTL: Native. */ if (proc == NULL) { Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "_"); - native = Tcl_DStringAppend(&newName, native, -1); + native = Tcl_DStringAppend(&newName, native, TCL_INDEX_NONE); proc = dlsym(handle, native); /* INTL: Native. */ Tcl_DStringFree(&newName); } @@ -194,8 +194,8 @@ FindSymbol( sprintf(buf, "%d", (int)Tcl_DStringLength(&ds)); Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "__Z"); - Tcl_DStringAppend(&newName, buf, -1); - Tcl_DStringAppend(&newName, Tcl_DStringValue(&ds), -1); + Tcl_DStringAppend(&newName, buf, TCL_INDEX_NONE); + Tcl_DStringAppend(&newName, Tcl_DStringValue(&ds), TCL_INDEX_NONE); TclDStringAppendLiteral(&newName, "P10Tcl_Interp"); native = Tcl_DStringValue(&newName); proc = dlsym(handle, native + 1); /* INTL: Native. */ diff --git a/unix/tclLoadDyld.c b/unix/tclLoadDyld.c index c2339db..cc3512d 100644 --- a/unix/tclLoadDyld.c +++ b/unix/tclLoadDyld.c @@ -185,7 +185,7 @@ TclpDlopen( nativePath = (const char *)Tcl_FSGetNativePath(pathPtr); nativeFileName = Tcl_UtfToExternalDString(NULL, TclGetString(pathPtr), - -1, &ds); + TCL_INDEX_NONE, &ds); #if TCL_DYLD_USE_DLFCN /* @@ -296,7 +296,7 @@ TclpDlopen( TclNewObj(errObj); if (errMsg != NULL) { - Tcl_AppendToObj(errObj, errMsg, -1); + Tcl_AppendToObj(errObj, errMsg, TCL_INDEX_NONE); } #if TCL_DYLD_USE_NSMODULE if (objFileImageErrMsg) { @@ -341,7 +341,7 @@ FindSymbol( Tcl_DString ds; const char *native; - native = Tcl_UtfToExternalDString(NULL, symbol, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, symbol, TCL_INDEX_NONE, &ds); if (dyldLoadHandle->dlHandle) { #if TCL_DYLD_USE_DLFCN proc = (Tcl_LibraryInitProc *)dlsym(dyldLoadHandle->dlHandle, native); @@ -360,7 +360,7 @@ FindSymbol( Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "_"); - native = Tcl_DStringAppend(&newName, native, -1); + native = Tcl_DStringAppend(&newName, native, TCL_INDEX_NONE); if (dyldLoadHandle->dyldLibHeader) { nsSymbol = NSLookupSymbolInImage(dyldLoadHandle->dyldLibHeader, native, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND_NOW | @@ -656,7 +656,7 @@ TclpLoadMemory( const char *errorName, *errMsg; NSLinkEditError(&editError, &errorNumber, &errorName, &errMsg); - Tcl_SetObjResult(interp, Tcl_NewStringObj(errMsg, -1)); + Tcl_SetObjResult(interp, Tcl_NewStringObj(errMsg, TCL_INDEX_NONE)); return TCL_ERROR; } diff --git a/unix/tclLoadNext.c b/unix/tclLoadNext.c index c50e5aa..23de2c5 100644 --- a/unix/tclLoadNext.c +++ b/unix/tclLoadNext.c @@ -83,7 +83,7 @@ TclpDlopen( Tcl_DString ds; - native = Tcl_UtfToExternalDString(NULL, fileName, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, fileName, TCL_INDEX_NONE, &ds); files = {native,NULL}; result = rld_load(errorStream, &header, files, NULL); Tcl_DStringFree(&ds); diff --git a/unix/tclLoadOSF.c b/unix/tclLoadOSF.c index bc49de2..852adca 100644 --- a/unix/tclLoadOSF.c +++ b/unix/tclLoadOSF.c @@ -100,7 +100,7 @@ TclpDlopen( Tcl_DString ds; - native = Tcl_UtfToExternalDString(NULL, fileName, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, fileName, TCL_INDEX_NONE, &ds); lm = (Tcl_LibraryInitProc *) load(native, LDR_NOFLAGS); Tcl_DStringFree(&ds); } diff --git a/unix/tclLoadShl.c b/unix/tclLoadShl.c index ad75a91..0889c21 100644 --- a/unix/tclLoadShl.c +++ b/unix/tclLoadShl.c @@ -86,7 +86,7 @@ TclpDlopen( Tcl_DString ds; - native = Tcl_UtfToExternalDString(NULL, fileName, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, fileName, TCL_INDEX_NONE, &ds); handle = shl_load(native, BIND_DEFERRED|BIND_VERBOSE|DYNAMIC_PATH, 0L); Tcl_DStringFree(&ds); } @@ -140,7 +140,7 @@ FindSymbol( (void *) &proc) != 0) { Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "_"); - Tcl_DStringAppend(&newName, symbol, -1); + Tcl_DStringAppend(&newName, symbol, TCL_INDEX_NONE); if (shl_findsym(&handle, Tcl_DStringValue(&newName), (short) TYPE_PROCEDURE, (void *) &proc) != 0) { proc = NULL; diff --git a/unix/tclUnixChan.c b/unix/tclUnixChan.c index 2e305be..c41cdd9 100644 --- a/unix/tclUnixChan.c +++ b/unix/tclUnixChan.c @@ -1797,12 +1797,11 @@ TclpGetDefaultStdChannel( * Some #def's to make the code a little clearer! */ -#define ZERO_OFFSET ((Tcl_SeekOffset) 0) #define ERROR_OFFSET ((Tcl_SeekOffset) -1) switch (type) { case TCL_STDIN: - if ((TclOSseek(0, ZERO_OFFSET, SEEK_CUR) == ERROR_OFFSET) + if ((TclOSseek(0, 0, SEEK_CUR) == ERROR_OFFSET) && (errno == EBADF)) { return NULL; } @@ -1811,7 +1810,7 @@ TclpGetDefaultStdChannel( bufMode = "line"; break; case TCL_STDOUT: - if ((TclOSseek(1, ZERO_OFFSET, SEEK_CUR) == ERROR_OFFSET) + if ((TclOSseek(1, 0, SEEK_CUR) == ERROR_OFFSET) && (errno == EBADF)) { return NULL; } @@ -1820,7 +1819,7 @@ TclpGetDefaultStdChannel( bufMode = "line"; break; case TCL_STDERR: - if ((TclOSseek(2, ZERO_OFFSET, SEEK_CUR) == ERROR_OFFSET) + if ((TclOSseek(2, 0, SEEK_CUR) == ERROR_OFFSET) && (errno == EBADF)) { return NULL; } @@ -1833,7 +1832,6 @@ TclpGetDefaultStdChannel( break; } -#undef ZERO_OFFSET #undef ERROR_OFFSET channel = Tcl_MakeFileChannel(INT2PTR(fd), mode); diff --git a/unix/tclUnixFCmd.c b/unix/tclUnixFCmd.c index 794a4a6..8109198 100644 --- a/unix/tclUnixFCmd.c +++ b/unix/tclUnixFCmd.c @@ -782,7 +782,7 @@ TclpObjCopyDirectory( Tcl_DStringFree(&dstString); if (ret != TCL_OK) { - *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1); + *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), TCL_INDEX_NONE); Tcl_DStringFree(&ds); Tcl_IncrRefCount(*errorPtr); } @@ -836,7 +836,7 @@ TclpObjRemoveDirectory( Tcl_DStringFree(&pathString); if (ret != TCL_OK) { - *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1); + *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), TCL_INDEX_NONE); Tcl_DStringFree(&ds); Tcl_IncrRefCount(*errorPtr); } @@ -886,7 +886,7 @@ DoRemoveDirectory( result = TCL_OK; if ((errno != EEXIST) || (recursive == 0)) { if (errorPtr != NULL) { - Tcl_ExternalToUtfDStringEx(NULL, path, -1, TCL_ENCODING_NOCOMPLAIN, errorPtr); + Tcl_ExternalToUtfDStringEx(NULL, path, TCL_INDEX_NONE, TCL_ENCODING_NOCOMPLAIN, errorPtr); } result = TCL_ERROR; } @@ -1018,9 +1018,9 @@ TraverseUnixTree( * Append name after slash, and recurse on the file. */ - Tcl_DStringAppend(sourcePtr, dirEntPtr->d_name, -1); + Tcl_DStringAppend(sourcePtr, dirEntPtr->d_name, TCL_INDEX_NONE); if (targetPtr != NULL) { - Tcl_DStringAppend(targetPtr, dirEntPtr->d_name, -1); + Tcl_DStringAppend(targetPtr, dirEntPtr->d_name, TCL_INDEX_NONE); } result = TraverseUnixTree(traverseProc, sourcePtr, targetPtr, errorPtr, doRewind); @@ -1135,7 +1135,7 @@ TraverseUnixTree( end: if (errfile != NULL) { if (errorPtr != NULL) { - Tcl_ExternalToUtfDStringEx(NULL, errfile, -1, TCL_ENCODING_NOCOMPLAIN, errorPtr); + Tcl_ExternalToUtfDStringEx(NULL, errfile, TCL_INDEX_NONE, TCL_ENCODING_NOCOMPLAIN, errorPtr); } result = TCL_ERROR; } @@ -1371,8 +1371,8 @@ GetGroupAttribute( Tcl_DString ds; const char *utf; - utf = Tcl_ExternalToUtfDString(NULL, groupPtr->gr_name, -1, &ds); - *attributePtrPtr = Tcl_NewStringObj(utf, -1); + utf = Tcl_ExternalToUtfDString(NULL, groupPtr->gr_name, TCL_INDEX_NONE, &ds); + *attributePtrPtr = Tcl_NewStringObj(utf, TCL_INDEX_NONE); Tcl_DStringFree(&ds); } return TCL_OK; @@ -1424,7 +1424,7 @@ GetOwnerAttribute( } else { Tcl_DString ds; - Tcl_ExternalToUtfDStringEx(NULL, pwPtr->pw_name, -1, TCL_ENCODING_NOCOMPLAIN, &ds); + Tcl_ExternalToUtfDStringEx(NULL, pwPtr->pw_name, TCL_INDEX_NONE, TCL_ENCODING_NOCOMPLAIN, &ds); *attributePtrPtr = TclDStringToObj(&ds); } return TCL_OK; @@ -2182,7 +2182,7 @@ TclUnixOpenTemporaryFile( Tcl_UtfToExternalDStringEx(NULL, string, length, TCL_ENCODING_NOCOMPLAIN, &templ); } else { Tcl_DStringInit(&templ); - Tcl_DStringAppend(&templ, DefaultTempDir(), -1); /* INTL: native */ + Tcl_DStringAppend(&templ, DefaultTempDir(), TCL_INDEX_NONE); /* INTL: native */ } TclDStringAppendLiteral(&templ, "/"); @@ -2307,7 +2307,7 @@ TclpCreateTemporaryDirectory( Tcl_UtfToExternalDStringEx(NULL, string, dirObj->length, TCL_ENCODING_NOCOMPLAIN, &templ); } else { Tcl_DStringInit(&templ); - Tcl_DStringAppend(&templ, DefaultTempDir(), -1); /* INTL: native */ + Tcl_DStringAppend(&templ, DefaultTempDir(), TCL_INDEX_NONE); /* INTL: native */ } if (Tcl_DStringValue(&templ)[Tcl_DStringLength(&templ) - 1] != '/') { diff --git a/unix/tclUnixFile.c b/unix/tclUnixFile.c index cda2cd3..780f1ea 100644 --- a/unix/tclUnixFile.c +++ b/unix/tclUnixFile.c @@ -119,7 +119,7 @@ TclpFindExecutable( TclDStringAppendLiteral(&buffer, "/"); } } - name = Tcl_DStringAppend(&buffer, argv0, -1); + name = Tcl_DStringAppend(&buffer, argv0, TCL_INDEX_NONE); /* * INTL: The following calls to access() and stat() should not be @@ -155,9 +155,9 @@ TclpFindExecutable( #endif { encoding = Tcl_GetEncoding(NULL, NULL); - Tcl_ExternalToUtfDStringEx(encoding, name, -1, TCL_ENCODING_NOCOMPLAIN, &utfName); + Tcl_ExternalToUtfDStringEx(encoding, name, TCL_INDEX_NONE, TCL_ENCODING_NOCOMPLAIN, &utfName); TclSetObjNameOfExecutable( - Tcl_NewStringObj(Tcl_DStringValue(&utfName), -1), encoding); + Tcl_NewStringObj(Tcl_DStringValue(&utfName), TCL_INDEX_NONE), encoding); Tcl_DStringFree(&utfName); goto done; } @@ -178,7 +178,7 @@ TclpFindExecutable( } Tcl_DStringInit(&nameString); - Tcl_DStringAppend(&nameString, name, -1); + Tcl_DStringAppend(&nameString, name, TCL_INDEX_NONE); Tcl_DStringFree(&buffer); Tcl_UtfToExternalDStringEx(NULL, Tcl_DStringValue(&cwd), @@ -191,10 +191,10 @@ TclpFindExecutable( Tcl_DStringFree(&nameString); encoding = Tcl_GetEncoding(NULL, NULL); - Tcl_ExternalToUtfDStringEx(encoding, Tcl_DStringValue(&buffer), -1, + Tcl_ExternalToUtfDStringEx(encoding, Tcl_DStringValue(&buffer), TCL_INDEX_NONE, TCL_ENCODING_NOCOMPLAIN, &utfName); TclSetObjNameOfExecutable( - Tcl_NewStringObj(Tcl_DStringValue(&utfName), -1), encoding); + Tcl_NewStringObj(Tcl_DStringValue(&utfName), TCL_INDEX_NONE), encoding); Tcl_DStringFree(&utfName); done: @@ -307,7 +307,7 @@ TclpMatchInDirectory( * Now open the directory for reading and iterate over the contents. */ - native = Tcl_UtfToExternalDString(NULL, dirName, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, dirName, TCL_INDEX_NONE, &ds); if ((TclOSstat(native, &statBuf) != 0) /* INTL: Native. */ || !S_ISDIR(statBuf.st_mode)) { @@ -371,14 +371,14 @@ TclpMatchInDirectory( * and pattern. If so, add the file to the result. */ - utfname = Tcl_ExternalToUtfDString(NULL, entryPtr->d_name, -1, + utfname = Tcl_ExternalToUtfDString(NULL, entryPtr->d_name, TCL_INDEX_NONE, &utfDs); if (Tcl_StringCaseMatch(utfname, pattern, 0)) { int typeOk = 1; if (types != NULL) { Tcl_DStringSetLength(&ds, nativeDirLen); - native = Tcl_DStringAppend(&ds, entryPtr->d_name, -1); + native = Tcl_DStringAppend(&ds, entryPtr->d_name, TCL_INDEX_NONE); matchResult = NativeMatchType(interp, native, entryPtr->d_name, types); typeOk = (matchResult == 1); @@ -598,7 +598,7 @@ TclpGetUserHome( { struct passwd *pwPtr; Tcl_DString ds; - const char *native = Tcl_UtfToExternalDString(NULL, name, -1, &ds); + const char *native = Tcl_UtfToExternalDString(NULL, name, TCL_INDEX_NONE, &ds); pwPtr = TclpGetPwNam(native); /* INTL: Native. */ Tcl_DStringFree(&ds); @@ -606,7 +606,7 @@ TclpGetUserHome( if (pwPtr == NULL) { return NULL; } - return Tcl_ExternalToUtfDString(NULL, pwPtr->pw_dir, -1, bufferPtr); + return Tcl_ExternalToUtfDString(NULL, pwPtr->pw_dir, TCL_INDEX_NONE, bufferPtr); } /* @@ -784,7 +784,7 @@ TclpGetCwd( } return NULL; } - return Tcl_ExternalToUtfDString(NULL, buffer, -1, bufferPtr); + return Tcl_ExternalToUtfDString(NULL, buffer, TCL_INDEX_NONE, bufferPtr); } /* @@ -819,7 +819,7 @@ TclpReadlink( const char *native; Tcl_DString ds; - native = Tcl_UtfToExternalDString(NULL, path, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, path, TCL_INDEX_NONE, &ds); length = readlink(native, link, sizeof(link)); /* INTL: Native. */ Tcl_DStringFree(&ds); @@ -1061,7 +1061,7 @@ TclpNativeToNormalized( { Tcl_DString ds; - Tcl_ExternalToUtfDStringEx(NULL, (const char *) clientData, -1, TCL_ENCODING_NOCOMPLAIN, &ds); + Tcl_ExternalToUtfDStringEx(NULL, (const char *) clientData, TCL_INDEX_NONE, TCL_ENCODING_NOCOMPLAIN, &ds); return TclDStringToObj(&ds); } diff --git a/unix/tclUnixInit.c b/unix/tclUnixInit.c index ec85fbe..cd84081 100644 --- a/unix/tclUnixInit.c +++ b/unix/tclUnixInit.c @@ -369,13 +369,13 @@ TclpInitPlatform(void) * Make sure, that the standard FDs exist. [Bug 772288] */ - if (TclOSseek(0, (Tcl_SeekOffset) 0, SEEK_CUR) == -1 && errno == EBADF) { + if (TclOSseek(0, 0, SEEK_CUR) == -1 && errno == EBADF) { open("/dev/null", O_RDONLY); } - if (TclOSseek(1, (Tcl_SeekOffset) 0, SEEK_CUR) == -1 && errno == EBADF) { + if (TclOSseek(1, 0, SEEK_CUR) == -1 && errno == EBADF) { open("/dev/null", O_WRONLY); } - if (TclOSseek(2, (Tcl_SeekOffset) 0, SEEK_CUR) == -1 && errno == EBADF) { + if (TclOSseek(2, 0, SEEK_CUR) == -1 && errno == EBADF) { open("/dev/null", O_WRONLY); } @@ -473,7 +473,7 @@ TclpInitLibraryPath( */ str = getenv("TCL_LIBRARY"); /* INTL: Native. */ - Tcl_ExternalToUtfDStringEx(NULL, str, -1, TCL_ENCODING_NOCOMPLAIN, &buffer); + Tcl_ExternalToUtfDStringEx(NULL, str, TCL_INDEX_NONE, TCL_ENCODING_NOCOMPLAIN, &buffer); str = Tcl_DStringValue(&buffer); if ((str != NULL) && (str[0] != '\0')) { @@ -496,7 +496,7 @@ TclpInitLibraryPath( * If TCL_LIBRARY is set, search there. */ - Tcl_ListObjAppendElement(NULL, pathPtr, Tcl_NewStringObj(str, -1)); + Tcl_ListObjAppendElement(NULL, pathPtr, Tcl_NewStringObj(str, TCL_INDEX_NONE)); Tcl_SplitPath(str, &pathc, &pathv); if ((pathc > 0) && (strcasecmp(installLib + 4, pathv[pathc-1]) != 0)) { @@ -537,7 +537,7 @@ TclpInitLibraryPath( str = defaultLibraryDir; } if (str[0] != '\0') { - objPtr = Tcl_NewStringObj(str, -1); + objPtr = Tcl_NewStringObj(str, TCL_INDEX_NONE); Tcl_ListObjAppendElement(NULL, pathPtr, objPtr); } } @@ -634,13 +634,13 @@ Tcl_GetEncodingNameFromEnvironment( */ Tcl_DStringInit(&ds); - encoding = Tcl_DStringAppend(&ds, nl_langinfo(CODESET), -1); + encoding = Tcl_DStringAppend(&ds, nl_langinfo(CODESET), TCL_INDEX_NONE); Tcl_UtfToLower(Tcl_DStringValue(&ds)); knownEncoding = SearchKnownEncodings(encoding); if (knownEncoding != NULL) { - Tcl_DStringAppend(bufPtr, knownEncoding, -1); + Tcl_DStringAppend(bufPtr, knownEncoding, TCL_INDEX_NONE); } else if (NULL != Tcl_GetEncoding(NULL, encoding)) { - Tcl_DStringAppend(bufPtr, encoding, -1); + Tcl_DStringAppend(bufPtr, encoding, TCL_INDEX_NONE); } Tcl_DStringFree(&ds); if (Tcl_DStringLength(bufPtr)) { @@ -672,14 +672,14 @@ Tcl_GetEncodingNameFromEnvironment( Tcl_DStringInit(&ds); p = encoding; - encoding = Tcl_DStringAppend(&ds, p, -1); + encoding = Tcl_DStringAppend(&ds, p, TCL_INDEX_NONE); Tcl_UtfToLower(Tcl_DStringValue(&ds)); knownEncoding = SearchKnownEncodings(encoding); if (knownEncoding != NULL) { - Tcl_DStringAppend(bufPtr, knownEncoding, -1); + Tcl_DStringAppend(bufPtr, knownEncoding, TCL_INDEX_NONE); } else if (NULL != Tcl_GetEncoding(NULL, encoding)) { - Tcl_DStringAppend(bufPtr, encoding, -1); + Tcl_DStringAppend(bufPtr, encoding, TCL_INDEX_NONE); } if (Tcl_DStringLength(bufPtr)) { Tcl_DStringFree(&ds); @@ -700,9 +700,9 @@ Tcl_GetEncodingNameFromEnvironment( if (*p != '\0') { knownEncoding = SearchKnownEncodings(p); if (knownEncoding != NULL) { - Tcl_DStringAppend(bufPtr, knownEncoding, -1); + Tcl_DStringAppend(bufPtr, knownEncoding, TCL_INDEX_NONE); } else if (NULL != Tcl_GetEncoding(NULL, p)) { - Tcl_DStringAppend(bufPtr, p, -1); + Tcl_DStringAppend(bufPtr, p, TCL_INDEX_NONE); } } Tcl_DStringFree(&ds); @@ -710,7 +710,7 @@ Tcl_GetEncodingNameFromEnvironment( return Tcl_DStringValue(bufPtr); } } - return Tcl_DStringAppend(bufPtr, TCL_DEFAULT_ENCODING, -1); + return Tcl_DStringAppend(bufPtr, TCL_DEFAULT_ENCODING, TCL_INDEX_NONE); } /* @@ -900,7 +900,7 @@ TclpSetVariables( unameOK = 1; - native = Tcl_ExternalToUtfDString(NULL, name.sysname, -1, &ds); + native = Tcl_ExternalToUtfDString(NULL, name.sysname, TCL_INDEX_NONE, &ds); Tcl_SetVar2(interp, "tcl_platform", "os", native, TCL_GLOBAL_ONLY); Tcl_DStringFree(&ds); @@ -963,7 +963,7 @@ TclpSetVariables( user = ""; Tcl_DStringInit(&ds); /* ensure cleanliness */ } else { - user = Tcl_ExternalToUtfDString(NULL, pwEnt->pw_name, -1, &ds); + user = Tcl_ExternalToUtfDString(NULL, pwEnt->pw_name, TCL_INDEX_NONE, &ds); } Tcl_SetVar2(interp, "tcl_platform", "user", user, TCL_GLOBAL_ONLY); @@ -1012,7 +1012,7 @@ TclpFindVariable( Tcl_DStringInit(&envString); for (i = 0, env = environ[i]; env != NULL; i++, env = environ[i]) { - p1 = Tcl_ExternalToUtfDString(NULL, env, -1, &envString); + p1 = Tcl_ExternalToUtfDString(NULL, env, TCL_INDEX_NONE, &envString); p2 = name; for (; *p2 == *p1; p1++, p2++) { diff --git a/unix/tclUnixPipe.c b/unix/tclUnixPipe.c index d45f45a..4c52406 100644 --- a/unix/tclUnixPipe.c +++ b/unix/tclUnixPipe.c @@ -141,7 +141,7 @@ TclpOpenFile( const char *native; Tcl_DString ds; - native = Tcl_UtfToExternalDString(NULL, fname, -1, &ds); + native = Tcl_UtfToExternalDString(NULL, fname, TCL_INDEX_NONE, &ds); fd = TclOSopen(native, mode, 0666); /* INTL: Native. */ Tcl_DStringFree(&ds); if (fd != -1) { @@ -153,7 +153,7 @@ TclpOpenFile( */ if ((mode & O_WRONLY) && !(mode & O_APPEND)) { - TclOSseek(fd, (Tcl_SeekOffset) 0, SEEK_END); + TclOSseek(fd, 0, SEEK_END); } /* @@ -198,14 +198,14 @@ TclpCreateTempFile( Tcl_DString dstring; char *native; - native = Tcl_UtfToExternalDString(NULL, contents, -1, &dstring); + native = Tcl_UtfToExternalDString(NULL, contents, TCL_INDEX_NONE, &dstring); if (write(fd, native, Tcl_DStringLength(&dstring)) == -1) { close(fd); Tcl_DStringFree(&dstring); return NULL; } Tcl_DStringFree(&dstring); - TclOSseek(fd, (Tcl_SeekOffset) 0, SEEK_SET); + TclOSseek(fd, 0, SEEK_SET); } return MakeFile(fd); } @@ -437,7 +437,7 @@ TclpCreateProcess( newArgv = (char **)TclStackAlloc(interp, (argc+1) * sizeof(char *)); newArgv[argc] = NULL; for (i = 0; i < argc; i++) { - newArgv[i] = Tcl_UtfToExternalDString(NULL, argv[i], -1, &dsArray[i]); + newArgv[i] = Tcl_UtfToExternalDString(NULL, argv[i], TCL_INDEX_NONE, &dsArray[i]); } #ifdef USE_VFORK diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index f579991..f413b5b 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -870,7 +870,7 @@ TcpGetOptionProc( errno = err; } if (errno != 0) { - Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(errno), -1); + Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(errno), TCL_INDEX_NONE); } return TCL_OK; } @@ -878,7 +878,7 @@ TcpGetOptionProc( if ((len > 1) && (optionName[1] == 'c') && (strncmp(optionName, "-connecting", len) == 0)) { Tcl_DStringAppend(dsPtr, - GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT) ? "1" : "0", -1); + GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT) ? "1" : "0", TCL_INDEX_NONE); return TCL_OK; } @@ -1766,13 +1766,13 @@ Tcl_OpenTcpServerEx( return statePtr->channel; } if (interp != NULL) { - Tcl_Obj *errorObj = Tcl_NewStringObj("couldn't open socket: ", -1); + Tcl_Obj *errorObj = Tcl_NewStringObj("couldn't open socket: ", TCL_INDEX_NONE); if (errorMsg == NULL) { errno = my_errno; - Tcl_AppendToObj(errorObj, Tcl_PosixError(interp), -1); + Tcl_AppendToObj(errorObj, Tcl_PosixError(interp), TCL_INDEX_NONE); } else { - Tcl_AppendToObj(errorObj, errorMsg, -1); + Tcl_AppendToObj(errorObj, errorMsg, TCL_INDEX_NONE); } Tcl_SetObjResult(interp, errorObj); } diff --git a/win/Makefile.in b/win/Makefile.in index 762d069..c982f02 100644 --- a/win/Makefile.in +++ b/win/Makefile.in @@ -897,8 +897,8 @@ install-libraries: libraries install-tzdata install-msgs done; @echo "Installing package msgcat 1.7.1 as a Tcl Module"; @$(COPY) $(ROOT_DIR)/library/msgcat/msgcat.tcl "$(MODULE_INSTALL_DIR)/9.0/msgcat-1.7.1.tm"; - @echo "Installing package tcltest 2.5.4 as a Tcl Module"; - @$(COPY) $(ROOT_DIR)/library/tcltest/tcltest.tcl "$(MODULE_INSTALL_DIR)/9.0/tcltest-2.5.4.tm"; + @echo "Installing package tcltest 2.5.5 as a Tcl Module"; + @$(COPY) $(ROOT_DIR)/library/tcltest/tcltest.tcl "$(MODULE_INSTALL_DIR)/9.0/tcltest-2.5.5.tm"; @echo "Installing package platform 1.0.18 as a Tcl Module"; @$(COPY) $(ROOT_DIR)/library/platform/platform.tcl "$(MODULE_INSTALL_DIR)/9.0/platform-1.0.18.tm"; @echo "Installing package platform::shell 1.1.4 as a Tcl Module"; diff --git a/win/nmakehlp.c b/win/nmakehlp.c index 71d727f..fc40da4 100644 --- a/win/nmakehlp.c +++ b/win/nmakehlp.c @@ -718,11 +718,13 @@ static int LocateDependencyHelper(const char *dir, const char *keypath) int keylen, ret; WIN32_FIND_DATA finfo; - if (dir == NULL || keypath == NULL) + if (dir == NULL || keypath == NULL) { return 2; /* Have no real error reporting mechanism into nmake */ + } dirlen = strlen(dir); - if ((dirlen + 3) > sizeof(path)) + if ((dirlen + 3) > sizeof(path)) { return 2; + } strncpy(path, dir, dirlen); strncpy(path+dirlen, "\\*", 3); /* Including terminating \0 */ keylen = strlen(keypath); @@ -788,8 +790,9 @@ static int LocateDependency(const char *keypath) for (i = 0; i < (sizeof(paths)/sizeof(paths[0])); ++i) { ret = LocateDependencyHelper(paths[i], keypath); - if (ret == 0) + if (ret == 0) { return ret; + } } return ret; } diff --git a/win/rules.vc b/win/rules.vc index db65ce7..fdc68e0 100644 --- a/win/rules.vc +++ b/win/rules.vc @@ -1418,7 +1418,7 @@ OPTDEFINES = $(OPTDEFINES) /DTCL_CFG_DO64BIT OPTDEFINES = $(OPTDEFINES) /DNO_STRTOI64=1
!endif
-!if "$(TCL_MAJOR_VERSION)" == "8"
+!if $(TCL_MAJOR_VERSION) == 8
!if "$(_USE_64BIT_TIME_T)" == "1"
OPTDEFINES = $(OPTDEFINES) /D_USE_64BIT_TIME_T=1
!endif
diff --git a/win/tclAppInit.c b/win/tclAppInit.c index 605b771..30127fd 100644 --- a/win/tclAppInit.c +++ b/win/tclAppInit.c @@ -15,17 +15,14 @@ */ #include "tcl.h" -#define WIN32_LEAN_AND_MEAN -#define STRICT /* See MSDN Article Q83456 */ -#include <windows.h> -#undef STRICT -#undef WIN32_LEAN_AND_MEAN -#include <locale.h> -#include <stdlib.h> -#include <tchar.h> -#if TCL_MAJOR_VERSION < 9 && TCL_MINOR_VERSION < 7 +#if TCL_MAJOR_VERSION < 9 +# if defined(USE_TCL_STUBS) +# error "Don't build with USE_TCL_STUBS!" +# endif +# if TCL_MINOR_VERSION < 7 # define Tcl_LibraryInitProc Tcl_PackageInitProc # define Tcl_StaticLibrary Tcl_StaticPackage +# endif #endif #ifdef TCL_TEST @@ -39,6 +36,14 @@ extern Tcl_LibraryInitProc Dde_Init; extern Tcl_LibraryInitProc Dde_SafeInit; #endif +#define WIN32_LEAN_AND_MEAN +#define STRICT /* See MSDN Article Q83456 */ +#include <windows.h> +#undef STRICT +#undef WIN32_LEAN_AND_MEAN +#include <locale.h> +#include <stdlib.h> +#include <tchar.h> #if defined(__GNUC__) || defined(TCL_BROKEN_MAINARGS) int _CRT_glob = 0; #endif /* __GNUC__ || TCL_BROKEN_MAINARGS */ @@ -136,7 +141,7 @@ _tmain( TclZipfs_AppHook(&argc, &argv); #endif - Tcl_Main(argc, argv, TCL_LOCAL_APPINIT); + Tcl_Main((size_t)argc, argv, TCL_LOCAL_APPINIT); return 0; /* Needed only to prevent compiler warning. */ } @@ -163,7 +168,7 @@ int Tcl_AppInit( Tcl_Interp *interp) /* Interpreter for application. */ { - if ((Tcl_Init)(interp) == TCL_ERROR) { + if (Tcl_Init(interp) == TCL_ERROR) { return TCL_ERROR; } @@ -210,8 +215,8 @@ Tcl_AppInit( * user-specific startup file will be run under any conditions. */ - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("tcl_rcFileName", -1), NULL, - Tcl_NewStringObj("~/tclshrc.tcl", -1), TCL_GLOBAL_ONLY); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("tcl_rcFileName", TCL_INDEX_NONE), NULL, + Tcl_NewStringObj("~/tclshrc.tcl", TCL_INDEX_NONE), TCL_GLOBAL_ONLY); return TCL_OK; } diff --git a/win/tclWinConsole.c b/win/tclWinConsole.c index 409890b..6956135 100644 --- a/win/tclWinConsole.c +++ b/win/tclWinConsole.c @@ -2,123 +2,195 @@ * tclWinConsole.c -- * * This file implements the Windows-specific console functions, and the - * "console" channel driver. + * "console" channel driver. Windows 7 or later required. * - * Copyright © 1999 Scriptics Corp. + * Copyright © 2022 Ashok P. Nadkarni * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ +#ifdef TCL_CONSOLE_DEBUG +#undef NDEBUG /* Enable asserts */ +#endif + #include "tclWinInt.h" +#include <assert.h> +#include <ctype.h> /* - * The following variable is used to tell whether this module has been - * initialized. + * A general note on the design: The console channel driver differs from most + * other drivers in the following respects: + * + * - There can be at most 3 console handles at any time since Windows does + * support allocation of more than one console (with three handles + * corresponding to stdin, stdout, stderr) + * + * - Consoles are created / inherited at process startup. There is currently + * no way in Tcl to programmatically create a console. Even if these were + * added the above Windows limitation would still apply. + * + * - Unlike files, sockets etc. where there is a one-to-one + * correspondence between Tcl channels and operating system handles, + * std* channels are shared amongst threads which means there can be + * multiple Tcl channels corresponding to a single console handle. + * + * - Even with multiple threads, more than one file event handler is unlikely. + * It does not make sense for multiple threads to register handlers for + * stdin because the input would be randomly fragmented amongst the threads. + * + * Various design factors are driven by the above, e.g. use of lists instead + * of hash tables (at most 3 console handles) and use of global instead of + * per thread queues which simplifies lock management particularly because + * thread-console relation is not one-one and is likely more performant as + * well with fewer locks needing to be obtained. + * + * Some additional design notes/reminders for the future: + * + * Aligned, synchronous reads are done directly by interpreter thread. + * Unaligned or asynchronous reads are done through the reader thread. + * + * The reader thread does not read ahead. That is, it will not post a read + * until some interpreter thread is actually requesting a read. This is + * because an interpreter may (for example) turn off echo for passwords and + * the read ahead would come in the way of that. + * + * If multiple threads are reading from stdin, the input is sprayed in random + * fashion. This is not good application design and hence no plan to address + * this (not clear what should be done even in theory) + * + * For output, we do not restrict all output to the console writer threads. + * See ConsoleOutputProc for the conditions. + * + * Locks are never held when calling the ReadConsole/WriteConsole API's + * since they may block. */ -static int initialized = 0; +static int gInitialized = 0; /* - * The consoleMutex locks around access to the initialized variable, and it is - * used to protect background threads from being terminated while they are - * using APIs that hold locks. + * Permit CONSOLE_BUFFER_SIZE to be defined on build command for stress test. + * + * In theory, at least sizeof(WCHAR) but note the Tcl channel bug + * https://core.tcl-lang.org/tcl/tktview/b3977d199b08e3979a8da970553d5209b3042e9c + * will cause failures in test suite if close to max input line in the suite. */ - -TCL_DECLARE_MUTEX(consoleMutex) +#ifndef CONSOLE_BUFFER_SIZE +#define CONSOLE_BUFFER_SIZE 8000 /* In bytes */ +#endif /* - * Bit masks used in the flags field of the ConsoleInfo structure below. + * Ring buffer for storing data. Actual data is from bufPtr[start]:bufPtr[size-1] + * and bufPtr[0]:bufPtr[length - (size-start)]. */ - -#define CONSOLE_PENDING (1<<0) /* Message is pending in the queue. */ -#define CONSOLE_ASYNC (1<<1) /* Channel is non-blocking. */ -#define CONSOLE_READ_OPS (1<<4) /* Channel supports read-related ops. */ -#define CONSOLE_RESET (1<<5) /* Console mode needs to be reset. */ +#if TCL_MAJOR_VERSION > 8 +typedef ptrdiff_t RingSizeT; /* Tcl9 TODO */ +#define RingSizeT_MAX PTRDIFF_MAX +#else +typedef int RingSizeT; +#define RingSizeT_MAX INT_MAX +#endif +typedef struct RingBuffer { + char *bufPtr; /* Pointer to buffer storage */ + RingSizeT capacity; /* Size of the buffer in RingBufferChar */ + RingSizeT start; /* Start of the data within the buffer. */ + RingSizeT length; /* Number of RingBufferChar*/ +} RingBuffer; +#define RingBufferLength(ringPtr_) ((ringPtr_)->length) +#define RingBufferHasFreeSpace(ringPtr_) ((ringPtr_)->length < (ringPtr_)->capacity) +#define RINGBUFFER_ASSERT(ringPtr_) assert(RingBufferCheck(ringPtr_)) /* - * Bit masks used in the sharedFlags field of the ConsoleInfo structure below. + * The Win32 console API does not support non-blocking I/O in any form. Thus + * the actual calls are made on a separate thread. Moreover, separate + * threads are needed for each handle because (for example) blocking on user + * input on stdin should not prevent output to stdout when non-blocking i/o + * is configured at the script level. + * + * In the input (e.g. stdin) case, the console stdin thread is the producer + * writing to the buffer ring buffer. The Tcl interpreter threads are the + * consumer. For the output (e.g. stdout/stderr) case, the Tcl interpreter + * are the producers while the console stdout/stderr threads are the + * consumers. + * + * Consoles are identified purely by handles and multiple threads may open + * them (as stdin/stdout/stderr are shared). + * + * Note on reference counting - a ConsoleHandleInfo instance has multiple + * references to it - one each from every channel that is attached to it + * plus one from the console thread itself which also serves as the reference + * from gConsoleHandleInfoList. */ - -#define CONSOLE_EOF (1<<2) /* Console has reached EOF. */ -#define CONSOLE_BUFFERED (1<<3) /* Data was read into a buffer by the reader - * thread. */ - -#define CONSOLE_BUFFER_SIZE (8*1024) - -/* - * Structure containing handles associated with one of the special console - * threads. - */ - -typedef struct { - HANDLE thread; /* Handle to reader or writer thread. */ - HANDLE readyEvent; /* Manual-reset event to signal _to_ the main - * thread when the worker thread has finished - * waiting for its normal work to happen. */ - TclPipeThreadInfo *TI; /* Thread info structure of writer and reader. */ -} ConsoleThreadInfo; +typedef struct ConsoleHandleInfo { + struct ConsoleHandleInfo *nextPtr; /* Process-global list of consoles */ + HANDLE console; /* Console handle */ + HANDLE consoleThread; /* Handle to thread doing actual i/o on the console */ + SRWLOCK lock; /* Controls access to this structure. + * Cheaper than CRITICAL_SECTION but note does not + * support recursive locks or Try* style attempts.*/ + CONDITION_VARIABLE consoleThreadCV;/* For awakening console thread */ + CONDITION_VARIABLE interpThreadCV; /* For awakening interpthread(s) */ + RingBuffer buffer; /* Buffer for data transferred between console + * threads and Tcl threads. For input consoles, + * written by the console thread and read by Tcl + * threads. The converse for output threads */ + DWORD initMode; /* Initial console mode. */ + DWORD lastError; /* An error caused by the last background + * operation. Set to 0 if no error has been + * detected. */ + int numRefs; /* See comments above */ + int permissions; /* TCL_READABLE for input consoles, TCL_WRITABLE + * for output. Only one or the other can be set. */ + int flags; +#define CONSOLE_DATA_AWAITED 0x0001 /* An interpreter is awaiting data */ +} ConsoleHandleInfo; /* * This structure describes per-instance data for a console based channel. + * + * Note on locking - this structure has no locks because it is accessed + * only from the thread owning channel EXCEPT when a console traverses it + * looking for a channel that is watching for events on the console. Even + * in that case, no locking is required because that access is only under + * the consoleLock lock which prevents the channel from being removed from + * the gWatchingChannelList which in turn means it will not be deallocated + * from under the console thread. Access to individual fields does not need + * to be controlled because + * - the console thread does not write to any fields + * - changes to the nextWatchingChannelPtr field + * - changes to other fields do not matter because after being read for + * queueing events, they are verified again when the event is received + * in the interpreter thread (since they could have changed anyways while + * the event was in-flight on the event queue) + * + * Note on reference counting - a structure instance may be referenced from + * three places: + * - the Tcl channel subsystem. This reference is created when on channel + * opening and dropped on channel close. This also covers the reference + * from gWatchingChannelList since queueing / dequeuing from that list + * happens in conjunction with channel operations. + * - the Tcl event queue entries. This reference is added when the event + * is queued and dropped on receipt. */ - -typedef struct ConsoleInfo { - HANDLE handle; - int type; - struct ConsoleInfo *nextPtr;/* Pointer to next registered console. */ +typedef struct ConsoleChannelInfo { + HANDLE handle; /* Console handle */ + Tcl_ThreadId threadId; /* Id of owning thread */ + struct ConsoleChannelInfo + *nextWatchingChannelPtr; /* Pointer to next channel watching events. */ Tcl_Channel channel; /* Pointer to channel structure. */ - int validMask; /* OR'ed combination of TCL_READABLE, + DWORD initMode; /* Initial console mode. */ + int numRefs; /* See comments above */ + int permissions; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, or TCL_EXCEPTION: indicates * which operations are valid on the file. */ int watchMask; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, or TCL_EXCEPTION: indicates * which events should be reported. */ - int flags; /* State flags, see above for a list. */ - Tcl_ThreadId threadId; /* Thread to which events should be reported. - * This value is used by the reader/writer - * threads. */ - ConsoleThreadInfo writer; /* A specialized thread for handling - * asynchronous writes to the console; the - * waiting starts when a control event is sent, - * and a reset event is sent back to the main - * thread when the write is done. */ - ConsoleThreadInfo reader; /* A specialized thread for handling - * asynchronous reads from the console; the - * waiting starts when a control event is sent, - * and a reset event is sent back to the main - * thread when input is available. */ - DWORD writeError; /* An error caused by the last background - * write. Set to 0 if no error has been - * detected. This word is shared with the - * writer thread so access must be - * synchronized with the writable object. */ - char *writeBuf; /* Current background output buffer. Access is - * synchronized with the writable object. */ - int writeBufLen; /* Size of write buffer. Access is - * synchronized with the writable object. */ - int toWrite; /* Current amount to be written. Access is - * synchronized with the writable object. */ - int readFlags; /* Flags that are shared with the reader - * thread. Access is synchronized with the - * readable object. */ - int bytesRead; /* Number of bytes in the buffer. */ - int offset; /* Number of bytes read out of the buffer. */ - DWORD initMode; /* Initial console mode. */ - char buffer[CONSOLE_BUFFER_SIZE]; - /* Data consumed by reader thread. */ -} ConsoleInfo; - -typedef struct { - /* - * The following pointer refers to the head of the list of consoles that - * are being watched for file events. - */ - - ConsoleInfo *firstConsolePtr; -} ThreadSpecificData; - -static Tcl_ThreadDataKey dataKey; + int flags; /* State flags */ +#define CONSOLE_EVENT_QUEUED 0x0001 /* Notification event already queued */ +#define CONSOLE_ASYNC 0x0002 /* Channel is non-blocking. */ +#define CONSOLE_READ_OPS 0x0004 /* Channel supports read-related ops. */ +} ConsoleChannelInfo; /* * The following structure is what is added to the Tcl event queue when @@ -126,51 +198,96 @@ static Tcl_ThreadDataKey dataKey; */ typedef struct { - Tcl_Event header; /* Information that is standard for all - * events. */ - ConsoleInfo *infoPtr; /* Pointer to console info structure. Note - * that we still have to verify that the - * console exists before dereferencing this - * pointer. */ + Tcl_Event header; /* Information that is standard for all events. */ + ConsoleChannelInfo *chanInfoPtr; /* Pointer to console info structure. Note + * that we still have to verify that the + * console exists before dereferencing this + * pointer. */ } ConsoleEvent; /* * Declarations for functions used only in this file. */ -static int ConsoleBlockModeProc(ClientData instanceData, - int mode); -static void ConsoleCheckProc(ClientData clientData, int flags); -static int ConsoleCloseProc(ClientData instanceData, - Tcl_Interp *interp, int flags); -static int ConsoleEventProc(Tcl_Event *evPtr, int flags); -static void ConsoleExitHandler(ClientData clientData); -static int ConsoleGetHandleProc(ClientData instanceData, - int direction, ClientData *handlePtr); -static int ConsoleGetOptionProc(ClientData instanceData, - Tcl_Interp *interp, const char *optionName, - Tcl_DString *dsPtr); -static void ConsoleInit(void); -static int ConsoleInputProc(ClientData instanceData, char *buf, - int toRead, int *errorCode); -static int ConsoleOutputProc(ClientData instanceData, - const char *buf, int toWrite, int *errorCode); +static int ConsoleBlockModeProc(ClientData instanceData, int mode); +static void ConsoleCheckProc(ClientData clientData, int flags); +static int ConsoleCloseProc(ClientData instanceData, + Tcl_Interp *interp, int flags); +static int ConsoleEventProc(Tcl_Event *evPtr, int flags); +static void ConsoleExitHandler(ClientData clientData); +static int ConsoleGetHandleProc(ClientData instanceData, + int direction, ClientData *handlePtr); +static int ConsoleGetOptionProc(ClientData instanceData, + Tcl_Interp *interp, const char *optionName, + Tcl_DString *dsPtr); +static void ConsoleInit(void); +static int ConsoleInputProc(ClientData instanceData, char *buf, + int toRead, int *errorCode); +static int ConsoleOutputProc(ClientData instanceData, + const char *buf, int toWrite, int *errorCode); +static int ConsoleSetOptionProc(ClientData instanceData, + Tcl_Interp *interp, const char *optionName, + const char *value); +static void ConsoleSetupProc(ClientData clientData, int flags); +static void ConsoleWatchProc(ClientData instanceData, int mask); +static void ProcExitHandler(ClientData clientData); +static void ConsoleThreadActionProc(ClientData instanceData, int action); +static DWORD ReadConsoleChars(HANDLE hConsole, WCHAR *lpBuffer, + RingSizeT nChars, RingSizeT *nCharsReadPtr); +static DWORD WriteConsoleChars(HANDLE hConsole, + const WCHAR *lpBuffer, RingSizeT nChars, + RingSizeT *nCharsWritten); +static void RingBufferInit(RingBuffer *ringPtr, RingSizeT capacity); +static void RingBufferClear(RingBuffer *ringPtr); +static RingSizeT RingBufferIn(RingBuffer *ringPtr, const char *srcPtr, + RingSizeT srcLen, int partialCopyOk); +static RingSizeT RingBufferOut(RingBuffer *ringPtr, char *dstPtr, + RingSizeT dstCapacity, int partialCopyOk); +static ConsoleHandleInfo *AllocateConsoleHandleInfo(HANDLE consoleHandle, + int permissions); +static ConsoleHandleInfo *FindConsoleInfo(const ConsoleChannelInfo *); static DWORD WINAPI ConsoleReaderThread(LPVOID arg); -static int ConsoleSetOptionProc(ClientData instanceData, - Tcl_Interp *interp, const char *optionName, - const char *value); -static void ConsoleSetupProc(ClientData clientData, int flags); -static void ConsoleWatchProc(ClientData instanceData, int mask); static DWORD WINAPI ConsoleWriterThread(LPVOID arg); -static void ProcExitHandler(ClientData clientData); -static int WaitForRead(ConsoleInfo *infoPtr, int blocking); -static void ConsoleThreadActionProc(ClientData instanceData, - int action); -static BOOL ReadConsoleBytes(HANDLE hConsole, LPVOID lpBuffer, - DWORD nbytes, LPDWORD nbytesread); -static BOOL WriteConsoleBytes(HANDLE hConsole, - const void *lpBuffer, DWORD nbytes, - LPDWORD nbyteswritten); +static void NudgeWatchers(HANDLE consoleHandle); +#ifndef NDEBUG +static int RingBufferCheck(const RingBuffer *ringPtr); +#endif + +/* + * Static data. + */ + +typedef struct { + /* Currently this struct is only used to detect thread initialization */ + int notUsed; /* Dummy field */ +} ThreadSpecificData; +static Tcl_ThreadDataKey dataKey; + +/* + * All access to static data is controlled through a single process-wide + * lock. A process can have only a single console at a time, with three + * handles for stdin, stdout and stderr. Creation/destruction of consoles is + * a relatively rare event (currently only possible during process start), + * the number of consoles (as opposed to channels) is small (only stdin, + * stdout and stderr), and contention low. More finer-grained locking would + * likely not only complicate implementation but be slower due to multiple + * locks being held. Note console channels also differ from other Tcl + * channel types in that the channel<->OS descriptor mapping is not one-to-one. + */ +SRWLOCK gConsoleLock; + + +/* Process-wide list of console handles. Access control through gConsoleLock */ +static ConsoleHandleInfo *gConsoleHandleInfoList; + +/* + * Process-wide list of channels that are listening for events. Again access + * control through gConsoleLock. Common list for all threads is simplifies + * locking and bookkeeping and is workable because in practice multiple + * threads are very unlikely to be all waiting on stdin (not workable + * because input would be randomly distributed to threads) + */ +static ConsoleChannelInfo *gWatchingChannelList; /* * This structure describes the channel type structure for command console @@ -178,82 +295,317 @@ static BOOL WriteConsoleBytes(HANDLE hConsole, */ static const Tcl_ChannelType consoleChannelType = { - "console", /* Type name. */ - TCL_CHANNEL_VERSION_5, /* v5 channel */ - NULL, /* Close proc. */ - ConsoleInputProc, /* Input proc. */ - ConsoleOutputProc, /* Output proc. */ - NULL, /* Seek proc. */ - ConsoleSetOptionProc, /* Set option proc. */ - ConsoleGetOptionProc, /* Get option proc. */ - ConsoleWatchProc, /* Set up notifier to watch the channel. */ - ConsoleGetHandleProc, /* Get an OS handle from channel. */ - ConsoleCloseProc, /* close2proc. */ - ConsoleBlockModeProc, /* Set blocking or non-blocking mode. */ - NULL, /* Flush proc. */ - NULL, /* Handler proc. */ - NULL, /* Wide seek proc. */ - ConsoleThreadActionProc, /* Thread action proc. */ - NULL /* Truncation proc. */ + "console", /* Type name. */ + TCL_CHANNEL_VERSION_5, /* v5 channel */ + NULL, /* Close proc. */ + ConsoleInputProc, /* Input proc. */ + ConsoleOutputProc, /* Output proc. */ + NULL, /* Seek proc. */ + ConsoleSetOptionProc, /* Set option proc. */ + ConsoleGetOptionProc, /* Get option proc. */ + ConsoleWatchProc, /* Set up notifier to watch the channel. */ + ConsoleGetHandleProc, /* Get an OS handle from channel. */ + ConsoleCloseProc, /* close2proc. */ + ConsoleBlockModeProc, /* Set blocking or non-blocking mode. */ + NULL, /* Flush proc. */ + NULL, /* Handler proc. */ + NULL, /* Wide seek proc. */ + ConsoleThreadActionProc, /* Thread action proc. */ + NULL /* Truncation proc. */ }; + +/* + *------------------------------------------------------------------------ + * + * RingBufferInit -- + * + * Initializes the ring buffer to a given size. + * + * Results: + * None. + * + * Side effects: + * Panics on allocation failure. + * + *------------------------------------------------------------------------ + */ +static void +RingBufferInit(RingBuffer *ringPtr, RingSizeT capacity) +{ + if (capacity <= 0 || capacity > RingSizeT_MAX) { + Tcl_Panic("Internal error: invalid ring buffer capacity requested."); + } + ringPtr->bufPtr = (char *)Tcl_Alloc(capacity); + ringPtr->capacity = capacity; + ringPtr->start = 0; + ringPtr->length = 0; +} /* - *---------------------------------------------------------------------- + *------------------------------------------------------------------------ * - * ReadConsoleBytes, WriteConsoleBytes -- + * RingBufferClear * - * Wrapper for ReadConsoleW, that takes and returns number of bytes - * instead of number of WCHARS. + * Clears the contents of a ring buffer. * - *---------------------------------------------------------------------- + * Results: + * None. + * + * Side effects: + * The allocated internal buffer is freed. + * + *------------------------------------------------------------------------ */ +static void +RingBufferClear(RingBuffer *ringPtr) +{ + if (ringPtr->bufPtr) { + Tcl_Free(ringPtr->bufPtr); + ringPtr->bufPtr = NULL; + } + ringPtr->capacity = 0; + ringPtr->start = 0; + ringPtr->length = 0; +} + +/* + *------------------------------------------------------------------------ + * + * RingBufferIn -- + * + * Appends data to the ring buffer. + * + * Results: + * Returns number of bytes copied. + * + * Side effects: + * Internal buffer is updated. + * + *------------------------------------------------------------------------ + */ +static RingSizeT +RingBufferIn( + RingBuffer *ringPtr, + const char *srcPtr, /* Source to be copied */ + RingSizeT srcLen, /* Length of source */ + int partialCopyOk /* If true, partial copy is permitted */ + ) +{ + RingSizeT freeSpace; + + RINGBUFFER_ASSERT(ringPtr); -static BOOL -ReadConsoleBytes( + freeSpace = ringPtr->capacity - ringPtr->length; + if (freeSpace < srcLen) { + if (!partialCopyOk) { + return 0; + } + /* Copy only as much as free space allows */ + srcLen = freeSpace; + } + + if (ringPtr->capacity - ringPtr->start > ringPtr->length) { + /* There is room at the back */ + RingSizeT endSpaceStart = ringPtr->start + ringPtr->length; + RingSizeT endSpace = ringPtr->capacity - endSpaceStart; + if (endSpace >= srcLen) { + /* Everything fits at the back */ + memmove(endSpaceStart + ringPtr->bufPtr, srcPtr, srcLen); + } else { + /* srcLen > endSpace */ + memmove(endSpaceStart + ringPtr->bufPtr, srcPtr, endSpace); + memmove(ringPtr->bufPtr, endSpace + srcPtr, srcLen - endSpace); + } + } else { + /* No room at the back. Existing data wrap to front. */ + RingSizeT wrapLen = + ringPtr->start + ringPtr->length - ringPtr->capacity; + memmove(wrapLen + ringPtr->bufPtr, srcPtr, srcLen); + } + + ringPtr->length += srcLen; + + RINGBUFFER_ASSERT(ringPtr); + + return srcLen; +} + +/* + *------------------------------------------------------------------------ + * + * RingBufferOut -- + * + * Moves data out of the ring buffer. If dstPtr is NULL, the data + * is simply removed. + * + * Results: + * Returns number of bytes copied or removed. + * + * Side effects: + * Internal buffer is updated. + * + *------------------------------------------------------------------------ + */ +static RingSizeT +RingBufferOut(RingBuffer *ringPtr, + char *dstPtr, /* Buffer for output data. May be NULL */ + RingSizeT dstCapacity, /* Size of buffer */ + int partialCopyOk) /* If true, return what's available */ +{ + RingSizeT leadLen; + + RINGBUFFER_ASSERT(ringPtr); + + if (dstCapacity > ringPtr->length) { + if (dstPtr && !partialCopyOk) { + return 0; + } + dstCapacity = ringPtr->length; + } + + if (ringPtr->start <= (ringPtr->capacity - ringPtr->length)) { + /* No content wrap around. So leadLen is entire content */ + leadLen = ringPtr->length; + } else { + /* Content wraps around so lead segment stretches to end of buffer */ + leadLen = ringPtr->capacity - ringPtr->start; + } + if (leadLen >= dstCapacity) { + if (dstPtr) { + memmove(dstPtr, ringPtr->start + ringPtr->bufPtr, dstCapacity); + } + ringPtr->start += dstCapacity; + } else { + RingSizeT wrapLen = dstCapacity - leadLen; + if (dstPtr) { + memmove(dstPtr, + ringPtr->start + ringPtr->bufPtr, + leadLen); + memmove( + leadLen + dstPtr, ringPtr->bufPtr, wrapLen); + } + ringPtr->start = wrapLen; + } + + ringPtr->length -= dstCapacity; + if (ringPtr->start == ringPtr->capacity || ringPtr->length == 0) { + ringPtr->start = 0; + } + + RINGBUFFER_ASSERT(ringPtr); + + return dstCapacity; +} + +#ifndef NDEBUG +static int +RingBufferCheck(const RingBuffer *ringPtr) +{ + return (ringPtr->bufPtr != NULL && ringPtr->capacity == CONSOLE_BUFFER_SIZE + && ringPtr->start < ringPtr->capacity + && ringPtr->length <= ringPtr->capacity); +} +#endif + +/* + *------------------------------------------------------------------------ + * + * ReadConsoleChars -- + * + * Wrapper for ReadConsoleW. + * + * Results: + * Returns 0 on success, else Windows error code. + * + * Side effects: + * On success the number of characters (not bytes) read is stored in + * *nCharsReadPtr. This will be 0 if the operation was interrupted by + * a Ctrl-C or a CancelIo call. + * + *------------------------------------------------------------------------ + */ +static DWORD +ReadConsoleChars( HANDLE hConsole, - LPVOID lpBuffer, - DWORD nbytes, - LPDWORD nbytesread) + WCHAR *lpBuffer, + RingSizeT nChars, + RingSizeT *nCharsReadPtr) { - DWORD ntchars; + DWORD nRead; BOOL result; /* - * If user types a Ctrl-Break or Ctrl-C, ReadConsole will return - * success with ntchars == 0 and GetLastError() will be - * ERROR_OPERATION_ABORTED. We do not want to treat this case - * as EOF so we will loop around again. If no Ctrl signal handlers - * have been established, the default signal OS handler in a separate - * thread will terminate the program. If a Ctrl signal handler - * has been established (through an extension for example), it - * will run and take whatever action it deems appropriate. + * If user types a Ctrl-Break or Ctrl-C, ReadConsole will return success + * with ntchars == 0 and GetLastError() will be ERROR_OPERATION_ABORTED. + * If no Ctrl signal handlers have been established, the default signal + * OS handler in a separate thread will terminate the program. If a Ctrl + * signal handler has been established (through an extension for + * example), it will run and take whatever action it deems appropriate. + * + * If one thread closes its channel, it calls CancelSynchronousIo on the + * console handle which results again in success being returned and + * GetLastError() being ERROR_OPERATION_ABORTED but ntchars in + * unmodified. + * + * In both cases above we will return success but with nbytesread as 0. + * This allows caller to check for thread termination etc. + * + * See https://bugs.python.org/issue30237 + * or https://github.com/microsoft/terminal/issues/12143 */ - do { - result = ReadConsoleW(hConsole, lpBuffer, nbytes / sizeof(WCHAR), &ntchars, - NULL); - } while (result && ntchars == 0 && GetLastError() == ERROR_OPERATION_ABORTED); - if (nbytesread != NULL) { - *nbytesread = ntchars * sizeof(WCHAR); - } - return result; + nRead = (DWORD)-1; + result = ReadConsoleW(hConsole, lpBuffer, nChars, &nRead, NULL); + if (result) { + if ((nRead == 0 || nRead == (DWORD)-1) + && GetLastError() == ERROR_OPERATION_ABORTED) { + nRead = 0; + } + *nCharsReadPtr = nRead; + return 0; + } else + return GetLastError(); } + +/* + *------------------------------------------------------------------------ + * + * WriteConsoleChars -- + * + * Wrapper for WriteConsoleW. + * + * Results: + * Returns 0 on success, Windows error code on failure. + * + * Side effects: + * On success the number of characters (not bytes) written is stored in + * *nCharsWrittenPtr. This will be 0 if the operation was interrupted by + * a Ctrl-C or a CancelIo call. + * + *------------------------------------------------------------------------ + */ -static BOOL -WriteConsoleBytes( +static DWORD +WriteConsoleChars( HANDLE hConsole, - const void *lpBuffer, - DWORD nbytes, - LPDWORD nbyteswritten) + const WCHAR *lpBuffer, + RingSizeT nChars, + RingSizeT *nCharsWrittenPtr) { - DWORD ntchars; + DWORD nCharsWritten; BOOL result; - result = WriteConsoleW(hConsole, lpBuffer, nbytes / sizeof(WCHAR), &ntchars, - NULL); - if (nbyteswritten != NULL) { - *nbyteswritten = ntchars * sizeof(WCHAR); + /* See comments in ReadConsoleChars, not sure that applies here */ + nCharsWritten = (DWORD)-1; + result = WriteConsoleW(hConsole, lpBuffer, nChars, &nCharsWritten, NULL); + if (result) { + if (nCharsWritten == (DWORD) -1) { + nCharsWritten = 0; + } + *nCharsWrittenPtr = nCharsWritten; + return 0; + } else { + return GetLastError(); } - return result; } /* @@ -280,19 +632,19 @@ ConsoleInit(void) * is a speed enhancement. */ - if (!initialized) { - Tcl_MutexLock(&consoleMutex); - if (!initialized) { - initialized = 1; + if (!gInitialized) { + AcquireSRWLockExclusive(&gConsoleLock); + if (!gInitialized) { + gInitialized = 1; Tcl_CreateExitHandler(ProcExitHandler, NULL); } - Tcl_MutexUnlock(&consoleMutex); + ReleaseSRWLockExclusive(&gConsoleLock); } if (TclThreadDataKeyGet(&dataKey) == NULL) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - tsdPtr->firstConsolePtr = NULL; + tsdPtr->notUsed = 0; Tcl_CreateEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL); Tcl_CreateThreadExitHandler(ConsoleExitHandler, NULL); } @@ -343,9 +695,46 @@ static void ProcExitHandler( TCL_UNUSED(ClientData)) { - Tcl_MutexLock(&consoleMutex); - initialized = 0; - Tcl_MutexUnlock(&consoleMutex); + AcquireSRWLockExclusive(&gConsoleLock); + gInitialized = 0; + ReleaseSRWLockExclusive(&gConsoleLock); +} + +/* + *------------------------------------------------------------------------ + * + * NudgeWatchers -- + * + * Wakes up all threads which have file event watchers on the passed + * console handle. + * + * The function locks and releases gConsoleLock. + * Caller must not be holding locks that will violate lock hierarchy. + * + * Results: + * None. + * + * Side effects: + * As above. + *------------------------------------------------------------------------ + */ +void NudgeWatchers (HANDLE consoleHandle) +{ + ConsoleChannelInfo *chanInfoPtr; + AcquireSRWLockShared(&gConsoleLock); /* Shared-read lock */ + for (chanInfoPtr = gWatchingChannelList; chanInfoPtr; + chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { + /* + * Notify channels interested in our handle AND that have + * a thread attached. + * No lock needed for chanInfoPtr. See ConsoleChannelInfo. + */ + if (chanInfoPtr->handle == consoleHandle + && chanInfoPtr->threadId != NULL) { + Tcl_ThreadAlert(chanInfoPtr->threadId); + } + } + ReleaseSRWLockShared(&gConsoleLock); } /* @@ -354,7 +743,9 @@ ProcExitHandler( * ConsoleSetupProc -- * * This procedure is invoked before Tcl_DoOneEvent blocks waiting for an - * event. + * event. It walks the channel list and if any input channel has data + * available or output channel has space for data, sets the event loop + * blocking time to 0 so that it will poll immediately. * * Results: * None. @@ -370,34 +761,45 @@ ConsoleSetupProc( TCL_UNUSED(ClientData), int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - ConsoleInfo *infoPtr; + ConsoleChannelInfo *chanInfoPtr; Tcl_Time blockTime = { 0, 0 }; int block = 1; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!(flags & TCL_FILE_EVENTS)) { return; } /* - * Look to see if any events are already pending. If they are, poll. + * Walk the list of channels. See general comments for struct + * ConsoleChannelInfo with regard to locking and field access. */ - - for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; - infoPtr = infoPtr->nextPtr) { - if (infoPtr->watchMask & TCL_WRITABLE) { - if (WaitForSingleObject(infoPtr->writer.readyEvent, - 0) != WAIT_TIMEOUT) { - block = 0; - } - } - if (infoPtr->watchMask & TCL_READABLE) { - if (WaitForRead(infoPtr, 0) >= 0) { - block = 0; + AcquireSRWLockShared(&gConsoleLock); /* READ lock - no data modification */ + + for (chanInfoPtr = gWatchingChannelList; block && chanInfoPtr != NULL; + chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { + ConsoleHandleInfo *handleInfoPtr; + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr != NULL) { + AcquireSRWLockShared(&handleInfoPtr->lock); + /* Remember at most one of READABLE, WRITABLE set */ + if (chanInfoPtr->watchMask & TCL_READABLE) { + if (RingBufferLength(&handleInfoPtr->buffer) > 0 + || handleInfoPtr->lastError != ERROR_SUCCESS) { + block = 0; /* Input data available */ + } + } else if (chanInfoPtr->watchMask & TCL_WRITABLE) { + if (RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { + /* TCL_WRITABLE */ + block = 0; /* Output space available */ + } } + ReleaseSRWLockShared(&handleInfoPtr->lock); } } + ReleaseSRWLockShared(&gConsoleLock); + if (!block) { + /* At least one channel is readable/writable. Set block time to 0 */ Tcl_SetMaxBlockTime(&blockTime); } } @@ -424,54 +826,85 @@ ConsoleCheckProc( TCL_UNUSED(ClientData), int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - ConsoleInfo *infoPtr; + ConsoleChannelInfo *chanInfoPtr; + Tcl_ThreadId me; int needEvent; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!(flags & TCL_FILE_EVENTS)) { return; } + me = Tcl_GetCurrentThread(); + /* - * Queue events for any ready consoles that don't already have events - * queued. + * Acquire a shared lock. Note this is ok even though we potentially + * modify the chanInfoPtr->flags because chanInfoPtr is only modified + * when it belongs to this thread and no other thread will write to it. + * THe shared lock is intended to protect the global gWatchingChannelList + * as we traverse it. */ + AcquireSRWLockShared(&gConsoleLock); + + for (chanInfoPtr = gWatchingChannelList; chanInfoPtr != NULL; + chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { + ConsoleHandleInfo *handleInfoPtr; - for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; - infoPtr = infoPtr->nextPtr) { - if (infoPtr->flags & CONSOLE_PENDING) { + if (chanInfoPtr->threadId != me) { + /* Some other thread owns the channel */ + continue; + } + if (chanInfoPtr->flags & CONSOLE_EVENT_QUEUED) { + /* A notification event already queued. No point in another. */ continue; } - /* - * Queue an event if the console is signaled for reading or writing. - */ + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + /* Pointer is safe to access as we are holding gConsoleLock */ + + if (handleInfoPtr == NULL) { + /* Stale event */ + continue; + } needEvent = 0; - if (infoPtr->watchMask & TCL_WRITABLE) { - if (WaitForSingleObject(infoPtr->writer.readyEvent, - 0) != WAIT_TIMEOUT) { - needEvent = 1; + AcquireSRWLockShared(&handleInfoPtr->lock); + /* Rememeber channel is read or write, never both */ + if (chanInfoPtr->watchMask & TCL_READABLE) { + if (RingBufferLength(&handleInfoPtr->buffer) > 0 + || handleInfoPtr->lastError != ERROR_SUCCESS) { + needEvent = 1; /* Input data available or error/EOF */ } + /* + * TCL_READABLE watch means someone is looking out for data being + * available, let reader thread know. Note channel need not be + * ASYNC! (Bug [baa51423c2]) + */ + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); } - - if (infoPtr->watchMask & TCL_READABLE) { - if (WaitForRead(infoPtr, 0) >= 0) { - needEvent = 1; + else if (chanInfoPtr->watchMask & TCL_WRITABLE) { + if (RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { + needEvent = 1; /* Output space available */ } } + ReleaseSRWLockShared(&handleInfoPtr->lock); if (needEvent) { ConsoleEvent *evPtr = (ConsoleEvent *)Tcl_Alloc(sizeof(ConsoleEvent)); - infoPtr->flags |= CONSOLE_PENDING; + /* See note above loop why this can be accessed without locks */ + chanInfoPtr->flags |= CONSOLE_EVENT_QUEUED; + chanInfoPtr->numRefs += 1; /* So it does not go away while event + is in queue */ evPtr->header.proc = ConsoleEventProc; - evPtr->infoPtr = infoPtr; + evPtr->chanInfoPtr = chanInfoPtr; Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); } } + + ReleaseSRWLockShared(&gConsoleLock); } - + /* *---------------------------------------------------------------------- * @@ -494,7 +927,7 @@ ConsoleBlockModeProc( int mode) /* TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { - ConsoleInfo *infoPtr = (ConsoleInfo *)instanceData; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; /* * Consoles on Windows can not be switched between blocking and @@ -505,9 +938,9 @@ ConsoleBlockModeProc( */ if (mode == TCL_MODE_NONBLOCKING) { - infoPtr->flags |= CONSOLE_ASYNC; + chanInfoPtr->flags |= CONSOLE_ASYNC; } else { - infoPtr->flags &= ~CONSOLE_ASYNC; + chanInfoPtr->flags &= ~CONSOLE_ASYNC; } return 0; } @@ -530,102 +963,102 @@ ConsoleBlockModeProc( static int ConsoleCloseProc( - ClientData instanceData, /* Pointer to ConsoleInfo structure. */ + ClientData instanceData, /* Pointer to ConsoleChannelInfo structure. */ TCL_UNUSED(Tcl_Interp *), int flags) { - ConsoleInfo *consolePtr = (ConsoleInfo *)instanceData; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + ConsoleHandleInfo *handleInfoPtr; int errorCode = 0; - ConsoleInfo *infoPtr, **nextPtrPtr; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ConsoleChannelInfo **nextPtrPtr; + int closeHandle; if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } - /* - * Clean up the background thread if necessary. Note that this must be - * done before we can close the file, since the thread may be blocking - * trying to read from the console. + * Don't close the Win32 handle if the handle is a standard channel + * during the thread exit process. Otherwise, one thread may kill the + * stdio of another while exiting. Note an explicit close in script will + * still close the handle. That's historical behavior on all platforms. */ + if (!TclInThreadExit() + || ((GetStdHandle(STD_INPUT_HANDLE) != chanInfoPtr->handle) + && (GetStdHandle(STD_OUTPUT_HANDLE) != chanInfoPtr->handle) + && (GetStdHandle(STD_ERROR_HANDLE) != chanInfoPtr->handle))) { + closeHandle = 1; + } else { + closeHandle = 0; + } - if (consolePtr->reader.thread) { - TclPipeThreadStop(&consolePtr->reader.TI, consolePtr->reader.thread); - CloseHandle(consolePtr->reader.thread); - CloseHandle(consolePtr->reader.readyEvent); - consolePtr->reader.thread = NULL; + AcquireSRWLockExclusive(&gConsoleLock); + + /* Remove channel from watchers' list */ + for (nextPtrPtr = &gWatchingChannelList; *nextPtrPtr != NULL; + nextPtrPtr = &(*nextPtrPtr)->nextWatchingChannelPtr) { + if (*nextPtrPtr == (ConsoleChannelInfo *) chanInfoPtr) { + *nextPtrPtr = (*nextPtrPtr)->nextWatchingChannelPtr; + break; + } } - consolePtr->validMask &= ~TCL_READABLE; - /* - * Wait for the writer thread to finish the current buffer, then terminate - * the thread and close the handles. If the channel is nonblocking, there - * should be no pending write operations. - */ + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr) { + /* + * Console thread may be blocked either waiting for console i/o + * or waiting on the condition variable for buffer empty/full + */ + AcquireSRWLockShared(&handleInfoPtr->lock); + + if (closeHandle) { + handleInfoPtr->console = INVALID_HANDLE_VALUE; + } - if (consolePtr->writer.thread) { - if (consolePtr->toWrite) { + /* Break the thread out of blocking console i/o */ + handleInfoPtr->numRefs -= 1; /* Remove reference from this channel */ + if (handleInfoPtr->numRefs == 1) { /* - * We only need to wait if there is something to write. This may - * prevent infinite wait on exit. [Python Bug 216289] + * Abort the i/o if no other threads are listening on it. + * Note without this check, an input line will be skipped on + * the cancel. */ - - WaitForSingleObject(consolePtr->writer.readyEvent, 5000); + CancelSynchronousIo(handleInfoPtr->consoleThread); } - TclPipeThreadStop(&consolePtr->writer.TI, consolePtr->writer.thread); - CloseHandle(consolePtr->writer.thread); - CloseHandle(consolePtr->writer.readyEvent); - consolePtr->writer.thread = NULL; - } - consolePtr->validMask &= ~TCL_WRITABLE; - - /* - * If the user has been tinkering with the mode, reset it now. We ignore - * any errors from this; we're quite possibly about to close or exit - * anyway. - */ + /* + * Wake up the console handling thread. Note we do not explicitly + * tell it handle is closed (below). It will find out on next access + */ + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); - if ((consolePtr->flags & CONSOLE_READ_OPS) && - (consolePtr->flags & CONSOLE_RESET)) { - SetConsoleMode(consolePtr->handle, consolePtr->initMode); + ReleaseSRWLockShared(&handleInfoPtr->lock); } - /* - * Don't close the Win32 handle if the handle is a standard channel during - * the thread exit process. Otherwise, one thread may kill the stdio of - * another. - */ + ReleaseSRWLockExclusive(&gConsoleLock); - if (!TclInThreadExit() - || ((GetStdHandle(STD_INPUT_HANDLE) != consolePtr->handle) - && (GetStdHandle(STD_OUTPUT_HANDLE) != consolePtr->handle) - && (GetStdHandle(STD_ERROR_HANDLE) != consolePtr->handle))) { - if (CloseHandle(consolePtr->handle) == FALSE) { + chanInfoPtr->channel = NULL; + chanInfoPtr->watchMask = 0; + chanInfoPtr->permissions = 0; + + if (closeHandle && chanInfoPtr->handle != INVALID_HANDLE_VALUE) { + if (CloseHandle(chanInfoPtr->handle) == FALSE) { Tcl_WinConvertError(GetLastError()); errorCode = errno; } + chanInfoPtr->handle = INVALID_HANDLE_VALUE; } - consolePtr->watchMask &= consolePtr->validMask; - /* - * Remove the file from the list of watched files. + * Note, we can check and manipulate numRefs without a lock because + * we have removed it from the watch queue so the console thread cannot + * get at it. */ - - for (nextPtrPtr = &tsdPtr->firstConsolePtr, infoPtr = *nextPtrPtr; - infoPtr != NULL; - nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { - if (infoPtr == (ConsoleInfo *) consolePtr) { - *nextPtrPtr = infoPtr->nextPtr; - break; - } - } - if (consolePtr->writeBuf != NULL) { - Tcl_Free(consolePtr->writeBuf); - consolePtr->writeBuf = 0; + if (chanInfoPtr->numRefs > 1) { + /* There may be references already on the event queue */ + chanInfoPtr->numRefs -= 1; + } else { + Tcl_Free(chanInfoPtr); } - Tcl_Free(consolePtr); return errorCode; } @@ -647,80 +1080,144 @@ ConsoleCloseProc( * *---------------------------------------------------------------------- */ - static int ConsoleInputProc( ClientData instanceData, /* Console state. */ - char *buf, /* Where to store data read. */ + char *bufPtr, /* Where to store data read. */ int bufSize, /* How much space is available in the * buffer? */ int *errorCode) /* Where to store error code. */ { - ConsoleInfo *infoPtr = (ConsoleInfo *)instanceData; - DWORD count, bytesRead = 0; - int result; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + ConsoleHandleInfo *handleInfoPtr; + RingSizeT numRead; - *errorCode = 0; - - /* - * Synchronize with the reader thread. - */ - - result = WaitForRead(infoPtr, (infoPtr->flags & CONSOLE_ASYNC) ? 0 : 1); + if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { + return 0; /* EOF */ + } - /* - * If an error occurred, return immediately. - */ + *errorCode = 0; - if (result == -1) { - *errorCode = errno; - return -1; + AcquireSRWLockShared(&gConsoleLock); + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr == NULL) { + /* Really shouldn't happen since channel is holding a reference */ + ReleaseSRWLockShared(&gConsoleLock); + return 0; /* EOF */ } + AcquireSRWLockExclusive(&handleInfoPtr->lock); + ReleaseSRWLockShared(&gConsoleLock); /* AFTER acquiring handleInfoPtr->lock */ - if (infoPtr->readFlags & CONSOLE_BUFFERED) { + while (1) { + numRead = RingBufferOut(&handleInfoPtr->buffer, bufPtr, bufSize, 1); /* - * Data is stored in the buffer. + * Note: even if channel is closed or has an error, as long there is + * buffered data, we will pass it up. */ + if (numRead != 0) { + /* If console thread was blocked, awaken it */ + if (chanInfoPtr->flags & CONSOLE_ASYNC) { + /* Async channels always want read ahead */ + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + } + break; + } + /* + * No data available. + * - If an error was recorded, generate that and reset it. + * - If EOF, indicate as much. It is up to the application to close + * the channel. + * - Otherwise, if non-blocking return EAGAIN or wait for more data. + */ + if (handleInfoPtr->lastError != 0) { + if (handleInfoPtr->lastError == ERROR_INVALID_HANDLE) { + numRead = 0; /* Treat as EOF */ + } else { + Tcl_WinConvertError(handleInfoPtr->lastError); + handleInfoPtr->lastError = 0; + *errorCode = Tcl_GetErrno(); + numRead = -1; + } + break; + } + if (handleInfoPtr->console == INVALID_HANDLE_VALUE) { + /* EOF - break with numRead == 0 */ + chanInfoPtr->handle = INVALID_HANDLE_VALUE; + break; + } - if (bufSize < (infoPtr->bytesRead - infoPtr->offset)) { - memcpy(buf, &infoPtr->buffer[infoPtr->offset], bufSize); - bytesRead = bufSize; - infoPtr->offset += bufSize; - } else { - memcpy(buf, &infoPtr->buffer[infoPtr->offset], bufSize); - bytesRead = infoPtr->bytesRead - infoPtr->offset; - - /* - * Reset the buffer. - */ + /* For async, tell caller we are blocked */ + if (chanInfoPtr->flags & CONSOLE_ASYNC) { + *errorCode = EWOULDBLOCK; + numRead = -1; + break; + } - infoPtr->readFlags &= ~CONSOLE_BUFFERED; - infoPtr->offset = 0; + /* + * Blocking read. Just get data from directly from console. There + * is a small complication in that we can only read even number + * of bytes (wide-character API) and the destination buffer should be + * WCHAR aligned. If either condition is not met, we defer to the + * reader thread which handles these case rather than dealing with + * them here (which is a little trickier than it might sound.) + */ + if ((1 & (ptrdiff_t)bufPtr) == 0 /* aligned buffer */ + && bufSize > 1 /* Not single byte read */ + ) { + DWORD lastError; + RingSizeT numChars; + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + lastError = ReadConsoleChars(chanInfoPtr->handle, + (WCHAR *)bufPtr, + bufSize / sizeof(WCHAR), + &numChars); + /* NOTE lock released so DON'T break. Return instead */ + if (lastError != ERROR_SUCCESS) { + Tcl_WinConvertError(lastError); + *errorCode = Tcl_GetErrno(); + return -1; + } else if (numChars > 0) { + /* Successfully read something. */ + return numChars * sizeof(WCHAR); + } else { + /* + * Ctrl-C/Ctrl-Brk interrupt. Loop around to retry. + * We have to reacquire the lock. No worried about handleInfoPtr + * having gone away since the channel holds a reference. + */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + continue; + } + } + /* + * Deferring blocking read to reader thread. + * Release the lock and sleep. Note that because the channel + * holds a reference count on handleInfoPtr, it will not + * be deallocated while the lock is released. + */ + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + if (!SleepConditionVariableSRW(&handleInfoPtr->interpThreadCV, + &handleInfoPtr->lock, + INFINITE, + 0)) { + Tcl_WinConvertError(GetLastError()); + *errorCode = Tcl_GetErrno(); + numRead = -1; + break; } - return bytesRead; + /* Lock is reacquired, loop back to try again */ } - /* - * Attempt to read bufSize bytes. The read will return immediately if - * there is any data available. Otherwise it will block until at least one - * byte is available or an EOF occurs. - */ - - if (ReadConsoleBytes(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, - &count) == TRUE) { - /* - * TODO: This potentially writes beyond the limits specified - * by the caller. In practice this is harmless, since all writes - * are into ChannelBuffers, and those have padding, but still - * ought to remove this, unless some Windows wizard can give - * a reason not to. - */ - buf[count] = '\0'; - return count; + if (chanInfoPtr->flags & CONSOLE_ASYNC) { + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); } - return -1; + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + return numRead; } /* @@ -740,7 +1237,6 @@ ConsoleInputProc( * *---------------------------------------------------------------------- */ - static int ConsoleOutputProc( ClientData instanceData, /* Console state. */ @@ -748,74 +1244,112 @@ ConsoleOutputProc( int toWrite, /* How many bytes to write? */ int *errorCode) /* Where to store error code. */ { - ConsoleInfo *infoPtr = (ConsoleInfo *)instanceData; - ConsoleThreadInfo *threadInfo = &infoPtr->writer; - DWORD bytesWritten, timeout; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + ConsoleHandleInfo *handleInfoPtr; + RingSizeT numWritten; *errorCode = 0; - /* avoid blocking if pipe-thread exited */ - timeout = (infoPtr->flags & CONSOLE_ASYNC) || !TclPipeThreadIsAlive(&threadInfo->TI) - || TclInExit() || TclInThreadExit() ? 0 : INFINITE; - if (WaitForSingleObject(threadInfo->readyEvent, timeout) == WAIT_TIMEOUT) { - /* - * The writer thread is blocked waiting for a write to complete and - * the channel is in non-blocking mode. - */ - - errno = EWOULDBLOCK; - goto error; + if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { + /* Some other thread would have *previously* closed the stdio handle */ + *errorCode = EPIPE; + return -1; } - /* - * Check for a background error on the last write. - */ - - if (infoPtr->writeError) { - Tcl_WinConvertError(infoPtr->writeError); - infoPtr->writeError = 0; - goto error; + AcquireSRWLockShared(&gConsoleLock); + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr == NULL) { + /* Really shouldn't happen since channel is holding a reference */ + *errorCode = EPIPE; + ReleaseSRWLockShared(&gConsoleLock); + return -1; } + AcquireSRWLockExclusive(&handleInfoPtr->lock); + ReleaseSRWLockShared(&gConsoleLock); /* AFTER acquiring handleInfoPtr->lock */ + + /* Keep looping until all written. Break out for async and errors */ + numWritten = 0; + while (1) { + /* Check for error and closing on every loop. */ + if (handleInfoPtr->lastError != 0) { + Tcl_WinConvertError(handleInfoPtr->lastError); + *errorCode = Tcl_GetErrno(); + numWritten = -1; + break; + } + if (handleInfoPtr->console == INVALID_HANDLE_VALUE) { + *errorCode = EPIPE; + chanInfoPtr->handle = INVALID_HANDLE_VALUE; + numWritten = -1; + break; + } - if (infoPtr->flags & CONSOLE_ASYNC) { /* - * The console is non-blocking, so copy the data into the output - * buffer and restart the writer thread. + * We can either write directly or through the console thread's + * ring buffer. We have to do the latter when + * (1) the operation is async since WriteConsoleChars is always blocking + * (2) when there is already data in the ring buffer because we don't + * want to reorder output from within a thread + * (3) when there are an odd number of bytes since WriteConsole + * takes whole WCHARs + * (4) when the pointer is not aligned on WCHAR + * The ring buffer deals with cases (3) and (4). It would be harder + * to duplicate that here. */ - - if (toWrite > infoPtr->writeBufLen) { + if ((chanInfoPtr->flags & CONSOLE_ASYNC) /* Case (1) */ + || RingBufferLength(&handleInfoPtr->buffer) != 0 /* Case (2) */ + || (toWrite & 1) != 0 /* Case (3) */ + || (PTR2INT(buf) & 1) != 0 /* Case (4) */ + ) { + numWritten += RingBufferIn(&handleInfoPtr->buffer, + numWritten + buf, + toWrite - numWritten, + 1); + if (numWritten == toWrite || chanInfoPtr->flags & CONSOLE_ASYNC) { + /* All done or async, just accept whatever was written */ + break; + } /* - * Reallocate the buffer to be large enough to hold the data. + * Release the lock and sleep. Note that because the channel + * holds a reference count on handleInfoPtr, it will not + * be deallocated while the lock is released. */ - - if (infoPtr->writeBuf) { - Tcl_Free(infoPtr->writeBuf); + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + if (!SleepConditionVariableSRW(&handleInfoPtr->interpThreadCV, + &handleInfoPtr->lock, + INFINITE, + 0)) { + /* Report the error */ + Tcl_WinConvertError(GetLastError()); + *errorCode = Tcl_GetErrno(); + numWritten = -1; + break; + } + } else { + /* Direct output */ + DWORD winStatus; + HANDLE consoleHandle = handleInfoPtr->console; + /* Unlock before blocking in WriteConsole */ + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + /* UNLOCKED so return, DON'T break out of loop as it will unlock again! */ + winStatus = WriteConsoleChars(consoleHandle, + (WCHAR *)buf, + toWrite / sizeof(WCHAR), + &numWritten); + if (winStatus == ERROR_SUCCESS) { + return numWritten * sizeof(WCHAR); + } else { + Tcl_WinConvertError(winStatus); + *errorCode = Tcl_GetErrno(); + return -1; } - infoPtr->writeBufLen = toWrite; - infoPtr->writeBuf = (char *)Tcl_Alloc(toWrite); } - memcpy(infoPtr->writeBuf, buf, toWrite); - infoPtr->toWrite = toWrite; - ResetEvent(threadInfo->readyEvent); - TclPipeThreadSignal(&threadInfo->TI); - bytesWritten = toWrite; - } else { - /* - * In the blocking case, just try to write the buffer directly. This - * avoids an unnecessary copy. - */ - if (WriteConsoleBytes(infoPtr->handle, buf, (DWORD) toWrite, - &bytesWritten) == FALSE) { - Tcl_WinConvertError(GetLastError()); - goto error; - } + /* Lock is reacquired. Continue loop */ } - return bytesWritten; - - error: - *errorCode = errno; - return -1; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + return numWritten; } /* @@ -846,66 +1380,84 @@ ConsoleEventProc( * such as TCL_FILE_EVENTS. */ { ConsoleEvent *consoleEvPtr = (ConsoleEvent *) evPtr; - ConsoleInfo *infoPtr; - int mask; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ConsoleChannelInfo *chanInfoPtr; + int freeChannel; + int mask = 0; if (!(flags & TCL_FILE_EVENTS)) { return 0; } + chanInfoPtr = consoleEvPtr->chanInfoPtr; /* - * Search through the list of watched consoles for the one whose handle - * matches the event. We do this rather than simply dereferencing the - * handle in the event so that consoles can be deleted while the event is - * in the queue. + * We know chanInfoPtr is valid because its reference count would have + * been incremented when the event was queued. The corresponding release + * happens in this function. */ - for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; - infoPtr = infoPtr->nextPtr) { - if (consoleEvPtr->infoPtr == infoPtr) { - infoPtr->flags &= ~CONSOLE_PENDING; - break; - } - } - /* - * Remove stale events. + * Global lock used for chanInfoPtr. A read (shared) lock suffices + * because all access is within the channel owning thread with the + * exception of watchers which is a read-only access. See comments + * to ConsoleChannelInfo. */ - - if (!infoPtr) { - return 1; - } + AcquireSRWLockShared(&gConsoleLock); + chanInfoPtr->flags &= ~CONSOLE_EVENT_QUEUED; /* - * Check to see if the console is readable. Note that we can't tell if a - * console is writable, so we always report it as being writable unless we - * have detected EOF. + * Only handle the event if the Tcl channel has not gone away AND is + * still owned by this thread AND is still watching events. */ - - mask = 0; - if (infoPtr->watchMask & TCL_WRITABLE) { - if (WaitForSingleObject(infoPtr->writer.readyEvent, - 0) != WAIT_TIMEOUT) { - mask = TCL_WRITABLE; - } - } - - if (infoPtr->watchMask & TCL_READABLE) { - if (WaitForRead(infoPtr, 0) >= 0) { - if (infoPtr->readFlags & CONSOLE_EOF) { + if (chanInfoPtr->channel && chanInfoPtr->threadId == Tcl_GetCurrentThread() + && (chanInfoPtr->watchMask & (TCL_READABLE|TCL_WRITABLE))) { + ConsoleHandleInfo *handleInfoPtr; + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr == NULL) { + /* Console was closed. EOF->read event only (not write) */ + if (chanInfoPtr->watchMask & TCL_READABLE) { mask = TCL_READABLE; - } else { - mask |= TCL_READABLE; } + } else { + AcquireSRWLockShared(&handleInfoPtr->lock); + /* Remember at most one of READABLE, WRITABLE set */ + if ((chanInfoPtr->watchMask & TCL_READABLE) + && RingBufferLength(&handleInfoPtr->buffer)) { + mask = TCL_READABLE; + } else if ((chanInfoPtr->watchMask & TCL_WRITABLE) + && RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { + /* Generate write event space available */ + mask = TCL_WRITABLE; + } + ReleaseSRWLockShared(&handleInfoPtr->lock); } } /* - * Inform the channel of the events. + * Tcl_NotifyChannel can recurse through the file event callback so need + * to release locks first. Our reference still holds so no danger of + * chanInfoPtr being deallocated if the callback closes the channel. */ + ReleaseSRWLockShared(&gConsoleLock); + if (mask) { + Tcl_NotifyChannel(chanInfoPtr->channel, mask); + /* Note: chanInfoPtr ref count may have changed */ + } + + /* No need to lock - see comments earlier */ + + /* Remove the reference to the channel from event record */ + if (chanInfoPtr->numRefs > 1) { + chanInfoPtr->numRefs -= 1; + freeChannel = 0; + } else { + assert(chanInfoPtr->channel == NULL); + freeChannel = 1; + } + + if (freeChannel) { + Tcl_Free(chanInfoPtr); + } - Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask); return 1; } @@ -928,39 +1480,51 @@ ConsoleEventProc( static void ConsoleWatchProc( ClientData instanceData, /* Console state. */ - int mask) /* What events to watch for, OR-ed combination - * of TCL_READABLE, TCL_WRITABLE and - * TCL_EXCEPTION. */ + int newMask) /* What events to watch for, one of + * of TCL_READABLE, TCL_WRITABLE + */ { - ConsoleInfo **nextPtrPtr, *ptr; - ConsoleInfo *infoPtr = (ConsoleInfo *)instanceData; - int oldMask = infoPtr->watchMask; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ConsoleChannelInfo **nextPtrPtr, *ptr; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + int oldMask = chanInfoPtr->watchMask; /* * Since most of the work is handled by the background threads, we just * need to update the watchMask and then force the notifier to poll once. */ - infoPtr->watchMask = mask & infoPtr->validMask; - if (infoPtr->watchMask) { + chanInfoPtr->watchMask = newMask & chanInfoPtr->permissions; + if (chanInfoPtr->watchMask) { Tcl_Time blockTime = { 0, 0 }; if (!oldMask) { - infoPtr->nextPtr = tsdPtr->firstConsolePtr; - tsdPtr->firstConsolePtr = infoPtr; + AcquireSRWLockExclusive(&gConsoleLock); + /* Add to list of watched channels */ + chanInfoPtr->nextWatchingChannelPtr = gWatchingChannelList; + gWatchingChannelList = chanInfoPtr; + + /* + * For read channels, need to tell the console reader thread + * that we are looking for data since it will not do reads until + * it knows someone is awaiting. + */ + ConsoleHandleInfo *handleInfoPtr; + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr) { + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + } + ReleaseSRWLockExclusive(&gConsoleLock); } Tcl_SetMaxBlockTime(&blockTime); } else if (oldMask) { - /* - * Remove the console from the list of watched consoles. - */ + /* Remove from list of watched channels */ - for (nextPtrPtr = &(tsdPtr->firstConsolePtr), ptr = *nextPtrPtr; + for (nextPtrPtr = &gWatchingChannelList, ptr = *nextPtrPtr; ptr != NULL; - nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { - if (infoPtr == ptr) { - *nextPtrPtr = ptr->nextPtr; + nextPtrPtr = &ptr->nextWatchingChannelPtr, ptr = *nextPtrPtr) { + if (chanInfoPtr == ptr) { + *nextPtrPtr = ptr->nextWatchingChannelPtr; break; } } @@ -991,116 +1555,59 @@ ConsoleGetHandleProc( TCL_UNUSED(int) /*direction*/, ClientData *handlePtr) /* Where to store the handle. */ { - ConsoleInfo *infoPtr = (ConsoleInfo *)instanceData; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - *handlePtr = infoPtr->handle; - return TCL_OK; + if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { + return TCL_ERROR; + } else { + *handlePtr = chanInfoPtr->handle; + return TCL_OK; + } } /* - *---------------------------------------------------------------------- + *------------------------------------------------------------------------ * - * WaitForRead -- + * ConsoleDataAvailable -- * - * Wait until some data is available, the console is at EOF or the reader - * thread is blocked waiting for data (if the channel is in non-blocking - * mode). + * Checks if there is data in the console input queue. * * Results: - * Returns 1 if console is readable. Returns 0 if there is no data on the - * console, but there is buffered data. Returns -1 if an error occurred. - * If an error occurred, the threads may not be synchronized. + * Returns 1 if the input queue has data, -1 on error else 0 if empty. * * Side effects: - * Updates the shared state flags. If no error occurred, the reader - * thread is blocked waiting for a signal from the main thread. + * None. * - *---------------------------------------------------------------------- + *------------------------------------------------------------------------ */ - -static int -WaitForRead( - ConsoleInfo *infoPtr, /* Console state. */ - int blocking) /* Indicates whether call should be blocking - * or not. */ + static int + ConsoleDataAvailable (HANDLE consoleHandle) { - DWORD timeout, count; - HANDLE *handle = (HANDLE *)infoPtr->handle; - ConsoleThreadInfo *threadInfo = &infoPtr->reader; - INPUT_RECORD input; - - while (1) { - /* - * Synchronize with the reader thread. - */ - - /* avoid blocking if pipe-thread exited */ - timeout = (!blocking || !TclPipeThreadIsAlive(&threadInfo->TI) - || TclInExit() || TclInThreadExit()) ? 0 : INFINITE; - if (WaitForSingleObject(threadInfo->readyEvent, timeout) == WAIT_TIMEOUT) { - /* - * The reader thread is blocked waiting for data and the channel - * is in non-blocking mode. - */ - - errno = EWOULDBLOCK; - return -1; - } - - /* - * At this point, the two threads are synchronized, so it is safe to - * access shared state. - */ - - /* - * If the console has hit EOF, it is always readable. - */ - - if (infoPtr->readFlags & CONSOLE_EOF) { - return 1; - } - - if (PeekConsoleInputW(handle, &input, 1, &count) == FALSE) { - /* - * Check to see if the peek failed because of EOF. - */ - - Tcl_WinConvertError(GetLastError()); - - if (errno == EOF) { - infoPtr->readFlags |= CONSOLE_EOF; - return 1; - } - - /* - * Ignore errors if there is data in the buffer. - */ - - if (infoPtr->readFlags & CONSOLE_BUFFERED) { - return 0; - } else { - return -1; - } - } + INPUT_RECORD input[5]; + DWORD count; + DWORD i; + /* + * Need at least one keyboard event. + */ + if (PeekConsoleInputW( + consoleHandle, input, sizeof(input) / sizeof(input[0]), &count) + == FALSE) { + return -1; + } + for (i = 0; i < count; ++i) { /* - * If there is data in the buffer, the console must be readable (since - * it is a line-oriented device). + * Event must be a keydown because a trailing LF keyup event is always + * present for line based input. */ - - if (infoPtr->readFlags & CONSOLE_BUFFERED) { + if (input[i].EventType == KEY_EVENT + && input[i].Event.KeyEvent.bKeyDown) { return 1; } - - /* - * There wasn't any data available, so reset the thread and try again. - */ - - ResetEvent(threadInfo->readyEvent); - TclPipeThreadSignal(&threadInfo->TI); } + return 0; } - + /* *---------------------------------------------------------------------- * @@ -1110,12 +1617,10 @@ WaitForRead( * available on a console. * * Results: - * None. + * Always 0. * * Side effects: - * Signals the main thread when input become available. May cause the - * main thread to wake up by posting a message. May one line from the - * console for each wait operation. + * Signals the main thread when input become available. * *---------------------------------------------------------------------- */ @@ -1124,76 +1629,178 @@ static DWORD WINAPI ConsoleReaderThread( LPVOID arg) { - TclPipeThreadInfo *pipeTI = (TclPipeThreadInfo *)arg; - ConsoleInfo *infoPtr = NULL; /* access info only after success init/wait */ - HANDLE *handle = NULL; - ConsoleThreadInfo *threadInfo = NULL; - int done = 0; + ConsoleHandleInfo *handleInfoPtr = (ConsoleHandleInfo *) arg; + ConsoleHandleInfo **iterator; + char inputChars[200]; /* Temporary buffer */ + RingSizeT inputLen = 0; + RingSizeT inputOffset = 0; - while (!done) { - /* - * Wait for the main thread to signal before attempting to read. - */ + /* + * Keep looping until one of the following happens. + * - there are no more channels listening on the console + * - the console handle has been closed + */ + + /* This thread is holding a reference so pointer is safe */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + + while (1) { - if (!TclPipeThreadWaitForSignal(&pipeTI)) { - /* exit */ + if (handleInfoPtr->numRefs == 1) { + /* + * Sole reference. That's this thread. Exit since no clients + * and no way for a thread to attach to a console after process + * start. + */ break; } - if (!infoPtr) { - infoPtr = (ConsoleInfo *)pipeTI->clientData; - handle = (HANDLE *)infoPtr->handle; - threadInfo = &infoPtr->reader; - } - /* - * Look for data on the console, but first ignore any events that are - * not KEY_EVENTs. + * Shared buffer has no data. If we have some in our private buffer + * copy that. Else check if there has been an error. In both cases + * notify the interp threads. */ + if (inputLen > 0 || handleInfoPtr->lastError != 0) { + HANDLE consoleHandle; + if (inputLen > 0) { + /* Private buffer has data. Copy it over. */ + RingSizeT nStored; + + assert((inputLen - inputOffset) > 0); + + nStored = RingBufferIn(&handleInfoPtr->buffer, + inputOffset + inputChars, + inputLen - inputOffset, + 1); + inputOffset += nStored; + if (inputOffset == inputLen) { + /* Temp buffer now empty */ + inputOffset = 0; + inputLen = 0; + } + } else { + /* + * On error, nothing but inform caller and wait + * We do not want to exit until there are no client interps. + */ + } - if (ReadConsoleBytes(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE, - (LPDWORD) &infoPtr->bytesRead) != FALSE) { /* - * Data was stored in the buffer. + * Wake up any threads waiting either synchronously or + * asynchronously. Since we are providing data, turn off the + * AWAITED flag. If the data provided is not sufficient the + * clients will request again. Note we have to wake up ALL + * awaiting threads, not just one, so they can all reissue + * requests if needed. (In a properly designed app, at most one + * thread should be reading standard input but...) */ + handleInfoPtr->flags &= ~CONSOLE_DATA_AWAITED; + /* Wake synchronous channels */ + WakeAllConditionVariable(&handleInfoPtr->interpThreadCV); + /* + * Wake up async channels registered for file events. Note in + * order to follow the locking hierarchy, we need to release + * handleInfoPtr->lock before calling NudgeWatchers. + */ + consoleHandle = handleInfoPtr->console; + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + NudgeWatchers(consoleHandle); + AcquireSRWLockExclusive(&handleInfoPtr->lock); - infoPtr->readFlags |= CONSOLE_BUFFERED; - } else { - DWORD err = GetLastError(); - - if (err == (DWORD) EOF) { - infoPtr->readFlags = CONSOLE_EOF; - } - done = 1; + /* + * Loop back to recheck for exit conditions changes while the + * the lock was not held. + */ + continue; } /* - * Signal the main thread by signalling the readable event and then - * waking up the notifier thread. + * Both shared buffer and private buffer are empty. Need to go get + * data from console but do not want to read ahead because the + * interp thread might change the read mode, e.g. turning off echo + * for password input. So only do so if at least one interpreter has + * requested data. */ - - SetEvent(threadInfo->readyEvent); - - /* - * Alert the foreground thread. Note that we need to treat this like a - * critical section so the foreground thread does not terminate this - * thread while we are holding a mutex in the notifier code. - */ - - Tcl_MutexLock(&consoleMutex); - if (infoPtr->threadId != NULL) { + if ((handleInfoPtr->flags & CONSOLE_DATA_AWAITED) + && ConsoleDataAvailable(handleInfoPtr->console)) { + DWORD error; + /* Do not hold the lock while blocked in console */ + ReleaseSRWLockExclusive(&handleInfoPtr->lock); /* - * TIP #218. When in flight ignore the event, no one will receive - * it anyway. + * Note - the temporary buffer serves two purposes. It */ + error = ReadConsoleChars(handleInfoPtr->console, + (WCHAR *)inputChars, + sizeof(inputChars) / sizeof(WCHAR), + &inputLen); + AcquireSRWLockExclusive(&handleInfoPtr->lock); + if (error == 0) { + inputLen *= sizeof(WCHAR); + } else { + /* + * We only store the last error. It is up to channel + * handlers whether to close or not in case of errors. + */ + handleInfoPtr->lastError = error; + if (handleInfoPtr->lastError == ERROR_INVALID_HANDLE) { + handleInfoPtr->console = INVALID_HANDLE_VALUE; + } + } + } else { + /* + * Either no one was asking for data, or no data was available. + * In the former case, wait until someone wakes us asking for + * data. In the latter case, there is no alternative but to + * poll since ReadConsole does not support async operation. + * So sleep for a short while and loop back to retry. + */ + DWORD sleepTime; + sleepTime = + handleInfoPtr->flags & CONSOLE_DATA_AWAITED ? 50 : INFINITE; + SleepConditionVariableSRW(&handleInfoPtr->consoleThreadCV, + &handleInfoPtr->lock, + sleepTime, + 0); + } + + /* Loop again to check for exit or wait for readers to wake us */ + } - Tcl_ThreadAlert(infoPtr->threadId); + /* + * Exiting: + * - remove the console from global list + * - close the handle if still valid + * - release the structure + * Note there is not need to check for any watchers because we only + * exit when there are no channels open to this console. + */ + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + AcquireSRWLockExclusive(&gConsoleLock); /* Modifying - exclusive lock */ + for (iterator = &gConsoleHandleInfoList; *iterator; + iterator = &(*iterator)->nextPtr) { + if (*iterator == handleInfoPtr) { + *iterator = handleInfoPtr->nextPtr; + break; } - Tcl_MutexUnlock(&consoleMutex); } + ReleaseSRWLockExclusive(&gConsoleLock); - /* Worker exit, so inform the main thread or free TI-structure (if owned) */ - TclPipeThreadExit(&pipeTI); + /* No need for relocking - no other thread should have access to it now */ + RingBufferClear(&handleInfoPtr->buffer); + + if (handleInfoPtr->console != INVALID_HANDLE_VALUE + && handleInfoPtr->lastError != ERROR_INVALID_HANDLE) { + SetConsoleMode(handleInfoPtr->console, handleInfoPtr->initMode); + /* + * NOTE: we do not call CloseHandle(handleInfoPtr->console) here. + * As per the GetStdHandle documentation, it need not be closed. + * Other components may be directly using it. Note however that + * an explicit chan close script command does close the handle + * for all threads. + */ + } + + Tcl_Free(handleInfoPtr); return 0; } @@ -1210,89 +1817,257 @@ ConsoleReaderThread( * Always returns 0. * * Side effects: - - * Signals the main thread when an output operation is completed. May - * cause the main thread to wake up by posting a message. + * Signals the main thread when an output operation is completed. * *---------------------------------------------------------------------- */ - static DWORD WINAPI -ConsoleWriterThread( - LPVOID arg) +ConsoleWriterThread(LPVOID arg) { - TclPipeThreadInfo *pipeTI = (TclPipeThreadInfo *)arg; - ConsoleInfo *infoPtr = NULL; /* access info only after success init/wait */ - HANDLE *handle = NULL; - ConsoleThreadInfo *threadInfo = NULL; - DWORD count, toWrite; - char *buf; - int done = 0; - - while (!done) { - /* - * Wait for the main thread to signal before attempting to write. - */ - if (!TclPipeThreadWaitForSignal(&pipeTI)) { - /* exit */ - break; - } - if (!infoPtr) { - infoPtr = (ConsoleInfo *)pipeTI->clientData; - handle = (HANDLE *)infoPtr->handle; - threadInfo = &infoPtr->writer; - } + ConsoleHandleInfo *handleInfoPtr = (ConsoleHandleInfo *) arg; + ConsoleHandleInfo **iterator; + BOOL success; + RingSizeT numBytes; + /* + * This buffer size has no relation really with the size of the shared + * buffer. Could be bigger or smaller. Make larger as multiple threads + * could potentially be writing to it. + */ + char buffer[2*CONSOLE_BUFFER_SIZE]; - buf = infoPtr->writeBuf; - toWrite = infoPtr->toWrite; + /* + * Keep looping until one of the following happens. + * + * - there are not more channels listening on the console + * - the console handle has been closed + * + * On each iteration, + * - if the channel buffer is empty, wait for some channel writer to write + * - if there is data in our buffer, write it to the console + */ + + /* This thread is holding a reference so pointer is safe */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + while (1) { + /* handleInfoPtr->lock must be held on entry to loop */ + + int offset; + HANDLE consoleHandle; /* - * Loop until all of the bytes are written or an error occurs. + * Sadly, we need to do another copy because do not want to hold + * a lock on handleInfoPtr->buffer while calling WriteConsole as that + * might block. Also, we only want to copy an integral number of + * WCHAR's, i.e. even number of chars so do some length checks up + * front. */ - - while (toWrite > 0) { - if (WriteConsoleBytes(handle, buf, (DWORD) toWrite, - &count) == FALSE) { - infoPtr->writeError = GetLastError(); - done = 1; + numBytes = RingBufferLength(&handleInfoPtr->buffer); + numBytes &= ~1; /* Copy integral number of WCHARs -> even number of bytes */ + if (numBytes == 0) { + /* No data to write */ + if (handleInfoPtr->numRefs == 1) { + /* + * Sole reference. That's this thread. Exit since no clients + * and no buffered output. + */ break; } - toWrite -= count; - buf += count; + /* Wake up any threads waiting synchronously. */ + WakeConditionVariable(&handleInfoPtr->interpThreadCV); + success = SleepConditionVariableSRW(&handleInfoPtr->consoleThreadCV, + &handleInfoPtr->lock, + INFINITE, + 0); + /* Note: lock has been acquired again! */ + if (!success && GetLastError() != ERROR_TIMEOUT) { + /* TODO - what can be done? Should not happen */ + /* For now keep going */ + } + continue; } - /* - * Signal the main thread by signalling the writable event and then - * waking up the notifier thread. - */ - - SetEvent(threadInfo->readyEvent); + /* We have data to write */ + if ((size_t)numBytes > (sizeof(buffer) / sizeof(buffer[0]))) { + numBytes = sizeof(buffer); + } + /* No need to check result, we already checked length bytes available */ + RingBufferOut(&handleInfoPtr->buffer, buffer, numBytes, 0); + + consoleHandle = handleInfoPtr->console; + WakeConditionVariable(&handleInfoPtr->interpThreadCV); + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + offset = 0; + while (numBytes > 0) { + RingSizeT numWChars = numBytes / sizeof(WCHAR); + DWORD status; + status = WriteConsoleChars(handleInfoPtr->console, + (WCHAR *)(offset + buffer), + numWChars, + &numWChars); + if (status != 0) { + /* Only overwrite if no previous error */ + if (handleInfoPtr->lastError == 0) { + handleInfoPtr->lastError = status; + } + if (status == ERROR_INVALID_HANDLE) { + handleInfoPtr->console = INVALID_HANDLE_VALUE; + } + /* Assume this write is done but keep looping in case + * it is a transient error. Not sure just closing handle + * and exiting thread is a good idea until all references + * from interp threads are gone. + */ + break; + } + numBytes -= numWChars * sizeof(WCHAR); + offset += numWChars * sizeof(WCHAR); + } + /* Wake up any threads waiting synchronously. */ + WakeConditionVariable(&handleInfoPtr->interpThreadCV); /* - * Alert the foreground thread. Note that we need to treat this like a - * critical section so the foreground thread does not terminate this - * thread while we are holding a mutex in the notifier code. + * Wake up all channels registered for file events. Note in + * order to follow the locking hierarchy, we cannot hold any locks + * when calling NudgeWatchers. */ + NudgeWatchers(consoleHandle); - Tcl_MutexLock(&consoleMutex); - if (infoPtr->threadId != NULL) { - /* - * TIP #218. When in flight ignore the event, no one will receive - * it anyway. - */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + } - Tcl_ThreadAlert(infoPtr->threadId); + /* + * Exiting: + * - remove the console from global list + * - release the structure + * NOTE: we do not call CloseHandle(handleInfoPtr->console) here. + * As per the GetStdHandle documentation, it need not be closed. + * Other components may be directly using it. Note however that + * an explicit chan close script command does close the handle + * for all threads. + */ + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + AcquireSRWLockExclusive(&gConsoleLock); /* Modifying - exclusive lock */ + for (iterator = &gConsoleHandleInfoList; *iterator; + iterator = &(*iterator)->nextPtr) { + if (*iterator == handleInfoPtr) { + *iterator = handleInfoPtr->nextPtr; + break; } - Tcl_MutexUnlock(&consoleMutex); } + ReleaseSRWLockExclusive(&gConsoleLock); + + RingBufferClear(&handleInfoPtr->buffer); - /* Worker exit, so inform the main thread or free TI-structure (if owned) */ - TclPipeThreadExit(&pipeTI); + Tcl_Free(handleInfoPtr); return 0; } /* + *------------------------------------------------------------------------ + * + * AllocateConsoleHandleInfo -- + * + * Allocates a ConsoleHandleInfo for the passed console handle. As + * a side effect starts a console thread to handle i/o on the handle. + * + * Important: Caller must be holding an EXCLUSIVE lock on gConsoleLock + * when calling this function. The lock continues to be held on return. + * + * Results: + * Pointer to an unlocked ConsoleHandleInfo structure. The reference + * count on the structure is 1. This corresponds to the common reference + * from the console thread and the gConsoleHandleInfoList. Returns NULL + * on error. + * + * Side effects: + * A console reader or writer thread is started. The returned structure + * is placed on the active console handler list gConsoleHandleInfoList. + * + *------------------------------------------------------------------------ + */ +static ConsoleHandleInfo * +AllocateConsoleHandleInfo( + HANDLE consoleHandle, + int permissions) /* TCL_READABLE or TCL_WRITABLE */ +{ + ConsoleHandleInfo *handleInfoPtr; + DWORD consoleMode; + + + handleInfoPtr = (ConsoleHandleInfo *)Tcl_Alloc(sizeof(*handleInfoPtr)); + handleInfoPtr->console = consoleHandle; + InitializeSRWLock(&handleInfoPtr->lock); + InitializeConditionVariable(&handleInfoPtr->consoleThreadCV); + InitializeConditionVariable(&handleInfoPtr->interpThreadCV); + RingBufferInit(&handleInfoPtr->buffer, CONSOLE_BUFFER_SIZE); + handleInfoPtr->lastError = 0; + handleInfoPtr->permissions = permissions; + handleInfoPtr->numRefs = 1; /* See function header */ + if (permissions == TCL_READABLE) { + GetConsoleMode(consoleHandle, &handleInfoPtr->initMode); + consoleMode = handleInfoPtr->initMode; + consoleMode &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT); + consoleMode |= ENABLE_LINE_INPUT; + SetConsoleMode(consoleHandle, consoleMode); + } + handleInfoPtr->consoleThread = CreateThread( + NULL, /* default security descriptor */ + 2*CONSOLE_BUFFER_SIZE, /* Stack size - gets rounded up to granularity */ + permissions == TCL_READABLE ? ConsoleReaderThread : ConsoleWriterThread, + handleInfoPtr, /* Pass to thread */ + 0, /* Flags - no special cases */ + NULL); /* Don't care about thread id */ + if (handleInfoPtr->consoleThread == NULL) { + /* Note - SRWLock and condition variables do not need finalization */ + RingBufferClear(&handleInfoPtr->buffer); + Tcl_Free(handleInfoPtr); + return NULL; + } + + /* Chain onto global list */ + handleInfoPtr->nextPtr = gConsoleHandleInfoList; + gConsoleHandleInfoList = handleInfoPtr; + + return handleInfoPtr; +} + +/* + *------------------------------------------------------------------------ + * + * FindConsoleInfo -- + * + * Finds the ConsoleHandleInfo record for a given ConsoleChannelInfo. + * The found record must match the console handle. It is the caller's + * responsibility to check the permissions (read/write) in the returned + * ConsoleHandleInfo match permissions in chanInfoPtr. This function does + * not check that. + * + * Important: Caller must be holding an shared or exclusive lock on + * gConsoleMutex. That ensures the returned pointer stays valid on + * return without risk of deallocation by other threads. + * + * Results: + * Pointer to the found ConsoleHandleInfo or NULL if not found + * + * Side effects: + * None. + * + *------------------------------------------------------------------------ + */ +static ConsoleHandleInfo * +FindConsoleInfo(const ConsoleChannelInfo *chanInfoPtr) +{ + ConsoleHandleInfo *handleInfoPtr; + for (handleInfoPtr = gConsoleHandleInfoList; handleInfoPtr; handleInfoPtr = handleInfoPtr->nextPtr) { + if (handleInfoPtr->console == chanInfoPtr->handle) { + return handleInfoPtr; + } + } + return NULL; +} + +/* *---------------------------------------------------------------------- * * TclWinOpenConsoleChannel -- @@ -1309,33 +2084,30 @@ ConsoleWriterThread( * *---------------------------------------------------------------------- */ - Tcl_Channel TclWinOpenConsoleChannel( HANDLE handle, char *channelName, int permissions) { - char encoding[4 + TCL_INTEGER_SPACE]; - ConsoleInfo *infoPtr; - DWORD modes; + ConsoleChannelInfo *chanInfoPtr; + ConsoleHandleInfo *handleInfoPtr; - ConsoleInit(); - - /* - * See if a channel with this handle already exists. - */ + /* A console handle can either be input or output, not both */ + if (permissions != TCL_READABLE && permissions != TCL_WRITABLE) { + return NULL; + } - infoPtr = (ConsoleInfo *)Tcl_Alloc(sizeof(ConsoleInfo)); - memset(infoPtr, 0, sizeof(ConsoleInfo)); + ConsoleInit(); - infoPtr->validMask = permissions; - infoPtr->handle = handle; - infoPtr->channel = (Tcl_Channel) NULL; + chanInfoPtr = (ConsoleChannelInfo *)Tcl_Alloc(sizeof(*chanInfoPtr)); + memset(chanInfoPtr, 0, sizeof(*chanInfoPtr)); - wsprintfA(encoding, "cp%d", GetConsoleCP()); + chanInfoPtr->permissions = permissions; + chanInfoPtr->handle = handle; + chanInfoPtr->channel = (Tcl_Channel) NULL; - infoPtr->threadId = Tcl_GetCurrentThread(); + chanInfoPtr->threadId = Tcl_GetCurrentThread(); /* * Use the pointer for the name of the result channel. This keeps the @@ -1343,10 +2115,7 @@ TclWinOpenConsoleChannel( * for instance). */ - sprintf(channelName, "file%" TCL_Z_MODIFIER "x", (size_t) infoPtr); - - infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName, - infoPtr, permissions); + sprintf(channelName, "file%" TCL_Z_MODIFIER "x", (size_t) chanInfoPtr); if (permissions & TCL_READABLE) { /* @@ -1355,38 +2124,76 @@ TclWinOpenConsoleChannel( * we only want to catch when complete lines are ready for reading. */ - infoPtr->flags |= CONSOLE_READ_OPS; - GetConsoleMode(infoPtr->handle, &infoPtr->initMode); - modes = infoPtr->initMode; - modes &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT); - modes |= ENABLE_LINE_INPUT; - SetConsoleMode(infoPtr->handle, modes); - - infoPtr->reader.readyEvent = CreateEventW(NULL, TRUE, TRUE, NULL); - infoPtr->reader.thread = CreateThread(NULL, 256, ConsoleReaderThread, - TclPipeThreadCreateTI(&infoPtr->reader.TI, infoPtr, - infoPtr->reader.readyEvent), 0, NULL); + chanInfoPtr->flags |= CONSOLE_READ_OPS; + GetConsoleMode(handle, &chanInfoPtr->initMode); + +#ifdef OBSOLETE + /* Why was priority being set on console input? Code smell */ SetThreadPriority(infoPtr->reader.thread, THREAD_PRIORITY_HIGHEST); +#endif + } else { + /* Already checked permissions is WRITABLE if not READABLE */ + /* TODO - enable ansi escape processing? */ } - if (permissions & TCL_WRITABLE) { + /* + * Global lock but that's ok. See comments top of file. Allocations + * will happen only a few times in the life of a process and that too + * generally at start up where only one thread is active. + */ + AcquireSRWLockExclusive(&gConsoleLock); /*Allocate needs exclusive lock */ - infoPtr->writer.readyEvent = CreateEventW(NULL, TRUE, TRUE, NULL); - infoPtr->writer.thread = CreateThread(NULL, 256, ConsoleWriterThread, - TclPipeThreadCreateTI(&infoPtr->writer.TI, infoPtr, - infoPtr->writer.readyEvent), 0, NULL); - SetThreadPriority(infoPtr->writer.thread, THREAD_PRIORITY_HIGHEST); + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr == NULL) { + /* Not found. Allocate one */ + handleInfoPtr = AllocateConsoleHandleInfo(handle, permissions); + } else { + /* Found. Its direction (read/write) better be the same */ + if (handleInfoPtr->permissions != permissions) { + handleInfoPtr = NULL; + } + } + + if (handleInfoPtr == NULL) { + ReleaseSRWLockExclusive(&gConsoleLock); + if (permissions == TCL_READABLE) { + SetConsoleMode(handle, chanInfoPtr->initMode); + } + Tcl_Free(chanInfoPtr); + return NULL; } /* - * Files have default translation of AUTO and ^Z eof char, which means + * There is effectively a reference to this structure from the Tcl + * channel subsystem. So record that. This reference will be dropped + * when the Tcl channel is closed. + */ + chanInfoPtr->numRefs = 1; + + /* + * Need to keep track of number of referencing channels for closing. + * The pointer is safe since there is a reference held to it from + * gConsoleHandleInfoList but still need to lock the structure itself + */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + handleInfoPtr->numRefs += 1; + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + + ReleaseSRWLockExclusive(&gConsoleLock); + + /* Note Tcl_CreateChannel never fails other than panic on error */ + chanInfoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName, + chanInfoPtr, permissions); + + /* + * Consoles have default translation of auto and ^Z eof char, which means * that a ^Z will be accepted as EOF when reading. */ - Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); - Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}"); - Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", "utf-16"); - return infoPtr->channel; + Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-translation", "auto"); + Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-eofchar", "\032 {}"); + Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-encoding", "utf-16"); + return chanInfoPtr->channel; } /* @@ -1410,33 +2217,15 @@ ConsoleThreadActionProc( ClientData instanceData, int action) { - ConsoleInfo *infoPtr = (ConsoleInfo *)instanceData; - - /* - * We do not access firstConsolePtr in the thread structures. This is not - * for all serials managed by the thread, but only those we are watching. - * Removal of the filevent handlers before transfer thus takes care of - * this structure. - */ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - Tcl_MutexLock(&consoleMutex); + /* No need for any locks as no other thread will be writing to it */ if (action == TCL_CHANNEL_THREAD_INSERT) { - /* - * We can't copy the thread information from the channel when the - * channel is created. At this time the channel back pointer has not - * been set yet. However in that case the threadId has already been - * set by TclpCreateCommandChannel itself, so the structure is still - * good. - */ - - ConsoleInit(); - if (infoPtr->channel != NULL) { - infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel); - } + ConsoleInit(); /* Needed to set up event source handlers for this thread */ + chanInfoPtr->threadId = Tcl_GetCurrentThread(); } else { - infoPtr->threadId = NULL; + chanInfoPtr->threadId = NULL; } - Tcl_MutexUnlock(&consoleMutex); } /* @@ -1456,7 +2245,6 @@ ConsoleThreadActionProc( * *---------------------------------------------------------------------- */ - static int ConsoleSetOptionProc( ClientData instanceData, /* File state. */ @@ -1464,7 +2252,7 @@ ConsoleSetOptionProc( const char *optionName, /* Which option to set? */ const char *value) /* New value for option. */ { - ConsoleInfo *infoPtr = (ConsoleInfo *)instanceData; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; int len = strlen(optionName); int vlen = strlen(value); @@ -1472,11 +2260,11 @@ ConsoleSetOptionProc( * Option -inputmode normal|password|raw */ - if ((infoPtr->flags & CONSOLE_READ_OPS) && (len > 1) && + if ((chanInfoPtr->flags & CONSOLE_READ_OPS) && (len > 1) && (strncmp(optionName, "-inputmode", len) == 0)) { DWORD mode; - if (GetConsoleMode(infoPtr->handle, &mode) == 0) { + if (GetConsoleMode(chanInfoPtr->handle, &mode) == 0) { Tcl_WinConvertError(GetLastError()); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( @@ -1486,18 +2274,18 @@ ConsoleSetOptionProc( return TCL_ERROR; } if (Tcl_UtfNcasecmp(value, "NORMAL", vlen) == 0) { - mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT; + mode |= + ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; } else if (Tcl_UtfNcasecmp(value, "PASSWORD", vlen) == 0) { - mode |= ENABLE_LINE_INPUT; + mode |= ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT; mode &= ~ENABLE_ECHO_INPUT; } else if (Tcl_UtfNcasecmp(value, "RAW", vlen) == 0) { - mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); + mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); } else if (Tcl_UtfNcasecmp(value, "RESET", vlen) == 0) { /* * Reset to the initial mode, whatever that is. */ - - mode = infoPtr->initMode; + mode = chanInfoPtr->initMode; } else { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( @@ -1508,7 +2296,7 @@ ConsoleSetOptionProc( } return TCL_ERROR; } - if (SetConsoleMode(infoPtr->handle, mode) == 0) { + if (SetConsoleMode(chanInfoPtr->handle, mode) == 0) { Tcl_WinConvertError(GetLastError()); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( @@ -1518,19 +2306,10 @@ ConsoleSetOptionProc( return TCL_ERROR; } - /* - * If we've changed the mode from default, schedule a reset later. - */ - - if (mode == infoPtr->initMode) { - infoPtr->flags &= ~CONSOLE_RESET; - } else { - infoPtr->flags |= CONSOLE_RESET; - } return TCL_OK; } - if (infoPtr->flags & CONSOLE_READ_OPS) { + if (chanInfoPtr->flags & CONSOLE_READ_OPS) { return Tcl_BadChannelOption(interp, optionName, "inputmode"); } else { return Tcl_BadChannelOption(interp, optionName, ""); @@ -1562,7 +2341,7 @@ ConsoleGetOptionProc( const char *optionName, /* Option to get. */ Tcl_DString *dsPtr) /* Where to store value(s). */ { - ConsoleInfo *infoPtr = (ConsoleInfo *)instanceData; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; int valid = 0; /* Flag if valid option parsed. */ unsigned int len; char buf[TCL_INTEGER_SPACE]; @@ -1580,7 +2359,7 @@ ConsoleGetOptionProc( * represents what almost all scripts really want to know. */ - if (infoPtr->flags & CONSOLE_READ_OPS) { + if (chanInfoPtr->flags & CONSOLE_READ_OPS) { if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-inputmode"); } @@ -1588,7 +2367,7 @@ ConsoleGetOptionProc( DWORD mode; valid = 1; - if (GetConsoleMode(infoPtr->handle, &mode) == 0) { + if (GetConsoleMode(chanInfoPtr->handle, &mode) == 0) { Tcl_WinConvertError(GetLastError()); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( @@ -1607,42 +2386,52 @@ ConsoleGetOptionProc( Tcl_DStringAppendElement(dsPtr, "raw"); } } - } - - /* - * Get option -winsize - * Option is readonly and returned by [fconfigure chan -winsize] but not - * returned by [fconfigure chan] without explicit option name. - */ + } else { + /* + * Output channel. Get option -winsize + * Option is readonly and returned by [fconfigure chan -winsize] but not + * returned by [fconfigure chan] without explicit option name. + */ + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-winsize"); + } - if ((len > 1) && (strncmp(optionName, "-winsize", len) == 0)) { - CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + if (len == 0 || (len > 1 && strncmp(optionName, "-winsize", len) == 0)) { + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; - valid = 1; - if (!GetConsoleScreenBufferInfo(infoPtr->handle, &consoleInfo)) { - Tcl_WinConvertError(GetLastError()); - if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read console size: %s", - Tcl_PosixError(interp))); + valid = 1; + if (!GetConsoleScreenBufferInfo(chanInfoPtr->handle, + &consoleInfo)) { + Tcl_WinConvertError(GetLastError()); + if (interp != NULL) { + Tcl_SetObjResult( + interp, + Tcl_ObjPrintf("couldn't read console size: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; } - return TCL_ERROR; + Tcl_DStringStartSublist(dsPtr); + sprintf(buf, + "%d", + consoleInfo.srWindow.Right - consoleInfo.srWindow.Left + 1); + Tcl_DStringAppendElement(dsPtr, buf); + sprintf(buf, + "%d", + consoleInfo.srWindow.Bottom - consoleInfo.srWindow.Top + 1); + Tcl_DStringAppendElement(dsPtr, buf); + Tcl_DStringEndSublist(dsPtr); } - sprintf(buf, "%d", - consoleInfo.srWindow.Right - consoleInfo.srWindow.Left + 1); - Tcl_DStringAppendElement(dsPtr, buf); - sprintf(buf, "%d", - consoleInfo.srWindow.Bottom - consoleInfo.srWindow.Top + 1); - Tcl_DStringAppendElement(dsPtr, buf); } + if (valid) { return TCL_OK; } - if (infoPtr->flags & CONSOLE_READ_OPS) { - return Tcl_BadChannelOption(interp, optionName, "inputmode winsize"); + if (chanInfoPtr->flags & CONSOLE_READ_OPS) { + return Tcl_BadChannelOption(interp, optionName, "inputmode"); } else { - return Tcl_BadChannelOption(interp, optionName, ""); + return Tcl_BadChannelOption(interp, optionName, "winsize"); } } diff --git a/win/tclWinDde.c b/win/tclWinDde.c index 2570954..1c10c65 100644 --- a/win/tclWinDde.c +++ b/win/tclWinDde.c @@ -1789,9 +1789,9 @@ DdeObjCmd( } if (result == TCL_OK) { - if (objc == 1) + if (objc == 1) { objPtr = objv[0]; - else { + } else { objPtr = Tcl_ConcatObj(objc, objv); } if (riPtr->handlerPtr != NULL) { diff --git a/win/tclWinFCmd.c b/win/tclWinFCmd.c index 025ac4b..a5d659e 100644 --- a/win/tclWinFCmd.c +++ b/win/tclWinFCmd.c @@ -330,8 +330,8 @@ DoRenameFile( Tcl_DStringInit(&srcString); Tcl_DStringInit(&dstString); - src = Tcl_WCharToUtfDString(nativeSrcPath, -1, &srcString); - dst = Tcl_WCharToUtfDString(nativeDstPath, -1, &dstString); + src = Tcl_WCharToUtfDString(nativeSrcPath, TCL_INDEX_NONE, &srcString); + dst = Tcl_WCharToUtfDString(nativeDstPath, TCL_INDEX_NONE, &dstString); /* * Check whether the destination path is actually inside the @@ -929,7 +929,7 @@ TclpObjCopyDirectory( } else if (!strcmp(Tcl_DStringValue(&ds), TclGetString(normDestPtr))) { *errorPtr = destPathPtr; } else { - *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1); + *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), TCL_INDEX_NONE); } Tcl_DStringFree(&ds); Tcl_IncrRefCount(*errorPtr); @@ -1117,7 +1117,7 @@ DoRemoveJustDirectory( char *p; Tcl_DStringInit(errorPtr); - p = Tcl_WCharToUtfDString(nativePath, -1, errorPtr); + p = Tcl_WCharToUtfDString(nativePath, TCL_INDEX_NONE, errorPtr); for (; *p; ++p) { if (*p == '\\') *p = '/'; } @@ -1332,7 +1332,7 @@ TraverseWinTree( Tcl_WinConvertError(GetLastError()); if (errorPtr != NULL) { Tcl_DStringInit(errorPtr); - Tcl_WCharToUtfDString(nativeErrfile, -1, errorPtr); + Tcl_WCharToUtfDString(nativeErrfile, TCL_INDEX_NONE, errorPtr); } result = TCL_ERROR; } @@ -1398,7 +1398,7 @@ TraversalCopy( if (errorPtr != NULL) { Tcl_DStringInit(errorPtr); - Tcl_WCharToUtfDString(nativeDst, -1, errorPtr); + Tcl_WCharToUtfDString(nativeDst, TCL_INDEX_NONE, errorPtr); } return TCL_ERROR; } @@ -1454,7 +1454,7 @@ TraversalDelete( if (errorPtr != NULL) { Tcl_DStringInit(errorPtr); - Tcl_WCharToUtfDString(nativeSrc, -1, errorPtr); + Tcl_WCharToUtfDString(nativeSrc, TCL_INDEX_NONE, errorPtr); } return TCL_ERROR; } @@ -1712,7 +1712,7 @@ ConvertFileNameFormat( */ Tcl_DStringInit(&dsTemp); - Tcl_WCharToUtfDString(nativeName, -1, &dsTemp); + Tcl_WCharToUtfDString(nativeName, TCL_INDEX_NONE, &dsTemp); Tcl_DStringFree(&ds); /* @@ -1952,14 +1952,14 @@ TclpObjListVolumes(void) buf[0] = (char) ('a' + i); if (GetVolumeInformationA(buf, NULL, 0, NULL, NULL, NULL, NULL, 0) || (GetLastError() == ERROR_NOT_READY)) { - elemPtr = Tcl_NewStringObj(buf, -1); + elemPtr = Tcl_NewStringObj(buf, TCL_INDEX_NONE); Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr); } } } else { for (p = buf; *p != '\0'; p += 4) { p[2] = '/'; - elemPtr = Tcl_NewStringObj(p, -1); + elemPtr = Tcl_NewStringObj(p, TCL_INDEX_NONE); Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr); } } @@ -2078,7 +2078,7 @@ TclpCreateTemporaryDirectory( */ Tcl_DStringInit(&name); - Tcl_WCharToUtfDString((LPCWSTR) Tcl_DStringValue(&base), -1, &name); + Tcl_WCharToUtfDString((LPCWSTR) Tcl_DStringValue(&base), TCL_INDEX_NONE, &name); Tcl_DStringFree(&base); return TclDStringToObj(&name); } diff --git a/win/tclWinFile.c b/win/tclWinFile.c index 9c40aad..3bb3117 100644 --- a/win/tclWinFile.c +++ b/win/tclWinFile.c @@ -869,7 +869,7 @@ TclpFindExecutable( GetModuleFileNameW(NULL, wName, sizeof(wName)/sizeof(WCHAR)); WideCharToMultiByte(CP_UTF8, 0, wName, -1, name, sizeof(name), NULL, NULL); TclWinNoBackslash(name); - TclSetObjNameOfExecutable(Tcl_NewStringObj(name, -1), NULL); + TclSetObjNameOfExecutable(Tcl_NewStringObj(name, TCL_INDEX_NONE), NULL); } /* @@ -1005,7 +1005,7 @@ TclpMatchInDirectory( * pattern. */ - dirName = Tcl_DStringAppend(&dsOrig, pattern, -1); + dirName = Tcl_DStringAppend(&dsOrig, pattern, TCL_INDEX_NONE); } else { dirName = TclDStringAppendLiteral(&dsOrig, "*.*"); } @@ -1084,7 +1084,7 @@ TclpMatchInDirectory( native = data.cFileName; attr = data.dwFileAttributes; Tcl_DStringInit(&ds); - utfname = Tcl_WCharToUtfDString(native, -1, &ds); + utfname = Tcl_WCharToUtfDString(native, TCL_INDEX_NONE, &ds); if (!matchSpecialDots) { /* @@ -1970,7 +1970,7 @@ TclpGetCwd( native += 2; } Tcl_DStringInit(bufferPtr); - Tcl_WCharToUtfDString(native, -1, bufferPtr); + Tcl_WCharToUtfDString(native, TCL_INDEX_NONE, bufferPtr); /* * Convert to forward slashes for easier use in scripts. @@ -2179,7 +2179,7 @@ NativeDev( GetFullPathNameW(nativePath, MAX_PATH, nativeFullPath, &nativePart); Tcl_DStringInit(&ds); - fullPath = Tcl_WCharToUtfDString(nativeFullPath, -1, &ds); + fullPath = Tcl_WCharToUtfDString(nativeFullPath, TCL_INDEX_NONE, &ds); if ((fullPath[0] == '\\') && (fullPath[1] == '\\')) { const char *p; @@ -2482,7 +2482,7 @@ TclpFilesystemPathType( Tcl_DString ds; Tcl_DStringInit(&ds); - Tcl_WCharToUtfDString(volType, -1, &ds); + Tcl_WCharToUtfDString(volType, TCL_INDEX_NONE, &ds); return TclDStringToObj(&ds); } #undef VOL_BUF_SIZE @@ -2630,7 +2630,7 @@ TclpObjNormalizePath( */ nextCheckpoint = 0; - Tcl_AppendToObj(to, currentPathEndPosition, -1); + Tcl_AppendToObj(to, currentPathEndPosition, TCL_INDEX_NONE); /* * Convert link to forward slashes. @@ -2806,7 +2806,7 @@ TclpObjNormalizePath( tmpPathPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), nextCheckpoint); - Tcl_AppendToObj(tmpPathPtr, lastValidPathEnd, -1); + Tcl_AppendToObj(tmpPathPtr, lastValidPathEnd, TCL_INDEX_NONE); path = Tcl_GetStringFromObj(tmpPathPtr, &length); Tcl_SetStringObj(pathPtr, path, length); Tcl_DecrRefCount(tmpPathPtr); @@ -2879,7 +2879,7 @@ TclWinVolumeRelativeNormalize( const char *drive = TclGetString(useThisCwd); absolutePath = Tcl_NewStringObj(drive,2); - Tcl_AppendToObj(absolutePath, path, -1); + Tcl_AppendToObj(absolutePath, path, TCL_INDEX_NONE); Tcl_IncrRefCount(absolutePath); /* @@ -2932,7 +2932,7 @@ TclWinVolumeRelativeNormalize( Tcl_AppendToObj(absolutePath, "/", 1); } Tcl_IncrRefCount(absolutePath); - Tcl_AppendToObj(absolutePath, path+2, -1); + Tcl_AppendToObj(absolutePath, path+2, TCL_INDEX_NONE); } *useThisCwdPtr = useThisCwd; return absolutePath; @@ -2969,7 +2969,7 @@ TclpNativeToNormalized( char *copy, *p; Tcl_DStringInit(&ds); - Tcl_WCharToUtfDString((const WCHAR *) clientData, -1, &ds); + Tcl_WCharToUtfDString((const WCHAR *) clientData, TCL_INDEX_NONE, &ds); copy = Tcl_DStringValue(&ds); len = Tcl_DStringLength(&ds); diff --git a/win/tclWinInit.c b/win/tclWinInit.c index 8e7ca8a..bbb0c81 100644 --- a/win/tclWinInit.c +++ b/win/tclWinInit.c @@ -233,7 +233,7 @@ AppendEnvironment( WideCharToMultiByte(CP_UTF8, 0, wBuf, -1, buf, MAX_PATH * 3, NULL, NULL); if (buf[0] != '\0') { - objPtr = Tcl_NewStringObj(buf, -1); + objPtr = Tcl_NewStringObj(buf, TCL_INDEX_NONE); Tcl_ListObjAppendElement(NULL, pathPtr, objPtr); TclWinNoBackslash(buf); @@ -257,7 +257,7 @@ AppendEnvironment( (void) Tcl_JoinPath(pathc, pathv, &ds); objPtr = TclDStringToObj(&ds); } else { - objPtr = Tcl_NewStringObj(buf, -1); + objPtr = Tcl_NewStringObj(buf, TCL_INDEX_NONE); } Tcl_ListObjAppendElement(NULL, pathPtr, objPtr); Tcl_Free((void *)pathv); @@ -503,11 +503,11 @@ TclpSetVariables( if (ptr == NULL) { ptr = Tcl_GetVar2(interp, "env", "HOMEDRIVE", TCL_GLOBAL_ONLY); if (ptr != NULL) { - Tcl_DStringAppend(&ds, ptr, -1); + Tcl_DStringAppend(&ds, ptr, TCL_INDEX_NONE); } ptr = Tcl_GetVar2(interp, "env", "HOMEPATH", TCL_GLOBAL_ONLY); if (ptr != NULL) { - Tcl_DStringAppend(&ds, ptr, -1); + Tcl_DStringAppend(&ds, ptr, TCL_INDEX_NONE); } if (Tcl_DStringLength(&ds) > 0) { Tcl_SetVar2(interp, "env", "HOME", Tcl_DStringValue(&ds), @@ -594,7 +594,7 @@ TclpFindVariable( */ Tcl_DStringInit(&envString); - envUpper = Tcl_WCharToUtfDString(env, -1, &envString); + envUpper = Tcl_WCharToUtfDString(env, TCL_INDEX_NONE, &envString); p1 = strchr(envUpper, '='); if (p1 == NULL) { continue; diff --git a/win/tclWinLoad.c b/win/tclWinLoad.c index f1a6640..5c3473c 100644 --- a/win/tclWinLoad.c +++ b/win/tclWinLoad.c @@ -114,10 +114,11 @@ TclpDlopen( * first error for reporting purposes. */ if (firstError == ERROR_MOD_NOT_FOUND || - firstError == ERROR_DLL_NOT_FOUND) + firstError == ERROR_DLL_NOT_FOUND) { lastError = GetLastError(); - else + } else { lastError = firstError; + } errMsg = Tcl_ObjPrintf("couldn't load library \"%s\": ", TclGetString(pathPtr)); @@ -219,7 +220,7 @@ FindSymbol( Tcl_DStringInit(&ds); TclDStringAppendLiteral(&ds, "_"); - sym2 = Tcl_DStringAppend(&ds, symbol, -1); + sym2 = Tcl_DStringAppend(&ds, symbol, TCL_INDEX_NONE); proc = (void *)GetProcAddress(hInstance, sym2); Tcl_DStringFree(&ds); } diff --git a/win/tclWinPipe.c b/win/tclWinPipe.c index a74ce85..c73c78a 100644 --- a/win/tclWinPipe.c +++ b/win/tclWinPipe.c @@ -679,7 +679,7 @@ TclpCreateTempFile( * Convert the contents from UTF to native encoding */ - native = Tcl_UtfToExternalDString(NULL, contents, -1, &dstring); + native = Tcl_UtfToExternalDString(NULL, contents, TCL_INDEX_NONE, &dstring); toCopy = Tcl_DStringLength(&dstring); for (p = native; toCopy > 0; p++, toCopy--) { @@ -1285,12 +1285,12 @@ ApplicationType( applType = APPL_NONE; Tcl_DStringInit(&nameBuf); - Tcl_DStringAppend(&nameBuf, originalName, -1); + Tcl_DStringAppend(&nameBuf, originalName, TCL_INDEX_NONE); nameLen = Tcl_DStringLength(&nameBuf); for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) { Tcl_DStringSetLength(&nameBuf, nameLen); - Tcl_DStringAppend(&nameBuf, extensions[i], -1); + Tcl_DStringAppend(&nameBuf, extensions[i], TCL_INDEX_NONE); Tcl_DStringInit(&ds); nativeName = Tcl_UtfToWCharDString(Tcl_DStringValue(&nameBuf), Tcl_DStringLength(&nameBuf), &ds); @@ -1311,7 +1311,7 @@ ApplicationType( continue; } Tcl_DStringInit(&ds); - strcpy(fullName, Tcl_WCharToUtfDString(nativeFullPath, -1, &ds)); + strcpy(fullName, Tcl_WCharToUtfDString(nativeFullPath, TCL_INDEX_NONE, &ds)); Tcl_DStringFree(&ds); ext = strrchr(fullName, '.'); @@ -1403,7 +1403,7 @@ ApplicationType( GetShortPathNameW(nativeFullPath, nativeFullPath, MAX_PATH); Tcl_DStringInit(&ds); - strcpy(fullName, Tcl_WCharToUtfDString(nativeFullPath, -1, &ds)); + strcpy(fullName, Tcl_WCharToUtfDString(nativeFullPath, TCL_INDEX_NONE, &ds)); Tcl_DStringFree(&ds); } return applType; @@ -1629,7 +1629,7 @@ BuildCommandLine( * Nothing to escape. */ - Tcl_DStringAppend(&ds, arg, -1); + Tcl_DStringAppend(&ds, arg, TCL_INDEX_NONE); } else { start = arg; for (special = arg; *special != '\0'; ) { diff --git a/win/tclWinSerial.c b/win/tclWinSerial.c index d306b11..a4ad3f3 100644 --- a/win/tclWinSerial.c +++ b/win/tclWinSerial.c @@ -1678,7 +1678,7 @@ SerialSetOptionProc( goto getStateFailed; } Tcl_DStringInit(&ds); - native = Tcl_UtfToWCharDString(value, -1, &ds); + native = Tcl_UtfToWCharDString(value, TCL_INDEX_NONE, &ds); result = BuildCommDCBW(native, &dcb); Tcl_DStringFree(&ds); @@ -1779,7 +1779,7 @@ SerialSetOptionProc( if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -xchar: should be a list of" - " two elements with each a single 8-bit character", -1)); + " two elements with each a single 8-bit character", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "XCHAR", NULL); } Tcl_Free((void *)argv); @@ -1853,7 +1853,7 @@ SerialSetOptionProc( (DWORD) (flag ? SETDTR : CLRDTR))) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( - "can't set DTR signal", -1)); + "can't set DTR signal", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", "TTY_SIGNAL", NULL); } @@ -1865,7 +1865,7 @@ SerialSetOptionProc( (DWORD) (flag ? SETRTS : CLRRTS))) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( - "can't set RTS signal", -1)); + "can't set RTS signal", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", "TTY_SIGNAL", NULL); } @@ -1877,7 +1877,7 @@ SerialSetOptionProc( (DWORD) (flag ? SETBREAK : CLRBREAK))) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( - "can't set BREAK signal", -1)); + "can't set BREAK signal", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", "TTY_SIGNAL", NULL); } diff --git a/win/tclWinSock.c b/win/tclWinSock.c index 5e3b7f4..06dce90 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -373,7 +373,7 @@ InitializeHostName( * Convert string from native to UTF then change to lowercase. */ - Tcl_UtfToLower(Tcl_WCharToUtfDString(wbuf, -1, &ds)); + Tcl_UtfToLower(Tcl_WCharToUtfDString(wbuf, TCL_INDEX_NONE, &ds)); } else { if (TclpHasSockets(NULL) == TCL_OK) { @@ -388,7 +388,7 @@ InitializeHostName( Tcl_DStringSetLength(&inDs, 256); if (gethostname(Tcl_DStringValue(&inDs), Tcl_DStringLength(&inDs)) == 0) { - Tcl_ExternalToUtfDStringEx(NULL, Tcl_DStringValue(&inDs), -1, + Tcl_ExternalToUtfDStringEx(NULL, Tcl_DStringValue(&inDs), TCL_INDEX_NONE, TCL_ENCODING_NOCOMPLAIN, &ds); } Tcl_DStringFree(&inDs); |