diff options
| -rw-r--r-- | doc/exec.n | 39 | ||||
| -rw-r--r-- | generic/tcl.h | 5 | ||||
| -rw-r--r-- | generic/tclBasic.c | 21 | ||||
| -rw-r--r-- | generic/tclCmdAH.c | 9 | ||||
| -rw-r--r-- | generic/tclDate.c | 6 | ||||
| -rw-r--r-- | generic/tclDecls.h | 14 | ||||
| -rw-r--r-- | generic/tclEncoding.c | 73 | ||||
| -rw-r--r-- | generic/tclFileName.c | 4 | ||||
| -rw-r--r-- | generic/tclGetDate.y | 4 | ||||
| -rw-r--r-- | generic/tclIO.c | 19 | ||||
| -rw-r--r-- | generic/tclInt.h | 13 | ||||
| -rw-r--r-- | generic/tclInterp.c | 9 | ||||
| -rw-r--r-- | generic/tclNamesp.c | 4 | ||||
| -rw-r--r-- | generic/tclTest.c | 2 | ||||
| -rw-r--r-- | generic/tclZipfs.c | 119 | ||||
| -rw-r--r-- | tests/io.test | 84 | ||||
| -rw-r--r-- | unix/Makefile.in | 2 | ||||
| -rw-r--r-- | unix/tclAppInit.c | 2 | ||||
| -rw-r--r-- | win/tclAppInit.c | 2 |
19 files changed, 256 insertions, 175 deletions
@@ -234,6 +234,45 @@ processor (\fBcmd.exe /c\fR), because this causes truncation of command-line (also the argument chain) on the first newline character. But it works properly with an executable (using CommandLineToArgv, etc). .PP +\fBArgument quoting\fR +.RS +The arguments of the \fBexec\fR command are mapped to the arguments of the called +program. Additional quote characters (\fB"\fR) are automatically added around +arguments if expected. Special characters are escaped by inserting backslash +characters. +.PP +The MS-Windows environment does execute programs mentioned in the arguments and +called batch files (conspec) replace environment variables, which may have side +effects (vulnerabilities) or break any already existing quoting (for example, +if the environment variable contains a special character like a \fB"\fR). +Examples are: +.CS +% exec my-echo.cmd {test&whoami} + test + mylogin +% exec my-echo.cmd "ENV X:%X%" + ENV X: CONTENT OF X +.CE +The following formatting is automatically performed on any +argument item: +.IP \(bu 3 +Avoid subprogram execution: +Any special character argument containing a special character (\fB&\fR, \fB|\fR, +\fB^\fR, \fB<\fR, \fB>\fR, \fB!\fR, \fB(\fR, \fB)\fR, \fB(\fR, \fB%\fR) +is automatically enclosed in quotes (\fB"\fR). Any data quote is escaped by +insertion of backslash characters. +.IP \(bu 3 +Avoid environment variable replacement: +Any appearence of environment variable reference (\fB%\fR) is individually quoted +by \fB"\fR. +.PP +TCL 8.6.10 refined this quoting by adding quoting for data quotes and individual +quoting of "\fB%\fR". +This may break present scripts which rely on the replacement functionality of +environment variables. +A solution with command parameters is envisaged for a future release of TCL. +.RE +.PP The Tk console text widget does not provide real standard IO capabilities. Under Tk, when redirecting from standard input, all applications will see an immediate end-of-file; information redirected to standard output or standard diff --git a/generic/tcl.h b/generic/tcl.h index 2f1f793..5769cbd 100644 --- a/generic/tcl.h +++ b/generic/tcl.h @@ -2180,10 +2180,9 @@ typedef struct Tcl_EncodingType { * changes, ensure ENCODING_PROFILE_* macros in tclInt.h are modified if * necessary. */ +#define TCL_ENCODING_PROFILE_STRICT TCL_ENCODING_STOPONERROR #define TCL_ENCODING_PROFILE_TCL8 0x01000000 -#define TCL_ENCODING_PROFILE_STRICT 0x02000000 -#define TCL_ENCODING_PROFILE_REPLACE 0x03000000 -#define TCL_ENCODING_PROFILE_DEFAULT TCL_ENCODING_PROFILE_TCL8 +#define TCL_ENCODING_PROFILE_REPLACE 0x02000000 /* * The following definitions are the error codes returned by the conversion diff --git a/generic/tclBasic.c b/generic/tclBasic.c index 0120466..b01717e 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -1082,14 +1082,9 @@ Tcl_CreateInterp(void) iPtr->deferredCallbacks = NULL; /* - * Create the core commands. Do it here, rather than calling - * Tcl_CreateCommand, because it's faster (there's no need to check for a - * preexisting command by the same name). If a command has a Tcl_CmdProc - * but no Tcl_ObjCmdProc, set the Tcl_ObjCmdProc to - * TclInvokeStringCommand. This is an object-based wrapper function that - * extracts strings, calls the string function, and creates an object for - * the result. Similarly, if a command has a Tcl_ObjCmdProc but no - * Tcl_CmdProc, set the Tcl_CmdProc to TclInvokeObjectCommand. + * Create the core commands. Do it here, rather than calling Tcl_CreateObjCommand, + * because it's faster (there's no need to check for a preexisting command + * by the same name). Set the Tcl_CmdProc to TclInvokeObjectCommand. */ for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) { @@ -3140,7 +3135,7 @@ TclRenameCommand( /* * Make sure that the destination command does not already exist. The * rename operation is like creating a command, so we should automatically - * create the containing namespaces just like Tcl_CreateCommand would. + * create the containing namespaces just like Tcl_CreateObjCommand would. */ TclGetNamespaceForQualName(interp, newName, NULL, @@ -3445,7 +3440,7 @@ Tcl_GetCommandInfoFromToken( * * Tcl_GetCommandName -- * - * Given a token returned by Tcl_CreateCommand, this function returns the + * Given a token returned by Tcl_CreateObjCommand, this function returns the * current name of the command (which may have changed due to renaming). * * Results: @@ -3461,7 +3456,7 @@ const char * Tcl_GetCommandName( TCL_UNUSED(Tcl_Interp *), Tcl_Command command) /* Token for command returned by a previous - * call to Tcl_CreateCommand. The command must + * call to Tcl_CreateObjCommand. The command must * not have been deleted. */ { Command *cmdPtr = (Command *) command; @@ -3484,7 +3479,7 @@ Tcl_GetCommandName( * * Tcl_GetCommandFullName -- * - * Given a token returned by, e.g., Tcl_CreateCommand or Tcl_FindCommand, + * Given a token returned by, e.g., Tcl_CreateObjCommand or Tcl_FindCommand, * this function appends to an object the command's full name, qualified * by a sequence of parent namespace names. The command's fully-qualified * name may have changed due to renaming. @@ -3503,7 +3498,7 @@ void Tcl_GetCommandFullName( Tcl_Interp *interp, /* Interpreter containing the command. */ Tcl_Command command, /* Token for command returned by a previous - * call to Tcl_CreateCommand. The command must + * call to Tcl_CreateObjCommand. The command must * not have been deleted. */ Tcl_Obj *objPtr) /* Points to the object onto which the * command's full name is appended. */ diff --git a/generic/tclCmdAH.c b/generic/tclCmdAH.c index 12216d4..86f1cda 100644 --- a/generic/tclCmdAH.c +++ b/generic/tclCmdAH.c @@ -673,7 +673,7 @@ EncodingConvertfromObjCmd( /* * Convert the string into a byte array in 'ds'. */ -#if !defined(TCL_NO_DEPRECATED) && (TCL_MAJOR_VERSION < 9) +#if !defined(TCL_NO_DEPRECATED) if (ENCODING_PROFILE_GET(flags) == TCL_ENCODING_PROFILE_TCL8) { /* Permits high bits to be non-0 in byte array (Tcl 8 style) */ bytesPtr = (char *) Tcl_GetByteArrayFromObj(data, &length); @@ -865,11 +865,13 @@ EncodingDirsObjCmd( return TCL_ERROR; } if (objc == 1) { +fprintf(stdout, "ED GET CALLER\n"); fflush(stdout); Tcl_SetObjResult(interp, Tcl_GetEncodingSearchPath()); return TCL_OK; } dirListObj = objv[1]; +fprintf(stdout, "ED SET CALLER\n"); fflush(stdout); if (Tcl_SetEncodingSearchPath(dirListObj) == TCL_ERROR) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected directory list but got \"%s\"", @@ -2212,7 +2214,7 @@ PathSplitCmd( Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } - res = Tcl_FSSplitPath(objv[1], NULL); + res = Tcl_FSSplitPath(objv[1], (Tcl_Size *)NULL); if (res == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": no such file or directory", @@ -3109,7 +3111,8 @@ ForeachAssignments( Tcl_Interp *interp, struct ForeachState *statePtr) { - int i, v, k; + int i; + Tcl_Size v, k; Tcl_Obj *valuePtr, *varValuePtr; for (i=0 ; i<statePtr->numLists ; i++) { diff --git a/generic/tclDate.c b/generic/tclDate.c index 44d45f9..2f05753 100644 --- a/generic/tclDate.c +++ b/generic/tclDate.c @@ -64,7 +64,6 @@ #define yylex TclDatelex #define yyerror TclDateerror #define yydebug TclDatedebug -#define yynerrs TclDatenerrs /* Copy the first part of user declarations. */ @@ -1294,9 +1293,6 @@ static YYLTYPE yyloc_default ; YYLTYPE yylloc = yyloc_default; - /* Number of syntax errors so far. */ - int yynerrs; - int yystate; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus; @@ -1360,7 +1356,6 @@ YYLTYPE yylloc = yyloc_default; yystate = 0; yyerrstatus = 0; - yynerrs = 0; yychar = YYEMPTY; /* Cause a token to be read. */ yylsp[0] = yylloc; goto yysetstate; @@ -2098,7 +2093,6 @@ yyerrlab: /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { - ++yynerrs; #if ! YYERROR_VERBOSE yyerror (&yylloc, info, YY_("syntax error")); #else diff --git a/generic/tclDecls.h b/generic/tclDecls.h index 0fe582e..5768233 100644 --- a/generic/tclDecls.h +++ b/generic/tclDecls.h @@ -4331,23 +4331,29 @@ extern const TclStubs *tclStubsPtr; #undef Tcl_GetIndexFromObjStruct #undef Tcl_GetBooleanFromObj #undef Tcl_GetBoolean +#ifdef __GNUC__ + /* If this gives: "error: size of array ‘_boolVar’ is negative", it means that sizeof(*boolPtr)>sizeof(int), which is not allowed */ +# define TCLBOOLWARNING(boolPtr) ({__attribute__((unused)) char _bool_Var[sizeof(*(boolPtr)) > sizeof(int) ? -1 : 1];}), +#else +# define TCLBOOLWARNING(boolPtr) +#endif #if defined(USE_TCL_STUBS) #define Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, offset, msg, flags, indexPtr) \ (tclStubsPtr->tcl_GetIndexFromObjStruct((interp), (objPtr), (tablePtr), (offset), (msg), (flags)|(int)(sizeof(*(indexPtr))<<1), (indexPtr))) #define Tcl_GetBooleanFromObj(interp, objPtr, boolPtr) \ - (sizeof(*(boolPtr)) == sizeof(int) ? tclStubsPtr->tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \ + (sizeof(*(boolPtr)) >= sizeof(int) ? (TCLBOOLWARNING(boolPtr)tclStubsPtr->tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr))) : \ Tcl_GetBoolFromObj(interp, objPtr, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr))) #define Tcl_GetBoolean(interp, src, boolPtr) \ - (sizeof(*(boolPtr)) == sizeof(int) ? tclStubsPtr->tcl_GetBoolean(interp, src, (int *)(boolPtr)) : \ + (sizeof(*(boolPtr)) >= sizeof(int) ? (TCLBOOLWARNING(boolPtr)tclStubsPtr->tcl_GetBoolean(interp, src, (int *)(boolPtr))) : \ Tcl_GetBool(interp, src, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr))) #else #define Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, offset, msg, flags, indexPtr) \ ((Tcl_GetIndexFromObjStruct)((interp), (objPtr), (tablePtr), (offset), (msg), (flags)|(int)(sizeof(*(indexPtr))<<1), (indexPtr))) #define Tcl_GetBooleanFromObj(interp, objPtr, boolPtr) \ - (sizeof(*(boolPtr)) == sizeof(int) ? Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \ + (sizeof(*(boolPtr)) >= sizeof(int) ? (TCLBOOLWARNING(boolPtr)Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr))) : \ Tcl_GetBoolFromObj(interp, objPtr, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr))) #define Tcl_GetBoolean(interp, src, boolPtr) \ - (sizeof(*(boolPtr)) == sizeof(int) ? Tcl_GetBoolean(interp, src, (int *)(boolPtr)) : \ + (sizeof(*(boolPtr)) >= sizeof(int) ? (TCLBOOLWARNING(boolPtr)Tcl_GetBoolean(interp, src, (int *)(boolPtr))) : \ Tcl_GetBool(interp, src, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr))) #endif diff --git a/generic/tclEncoding.c b/generic/tclEncoding.c index b441bf63..ff73904 100644 --- a/generic/tclEncoding.c +++ b/generic/tclEncoding.c @@ -192,7 +192,7 @@ Tcl_Encoding tclUtf8Encoding = NULL; * Names of encoding profiles and corresponding integer values. * Keep alphabetical order for error messages. */ -static struct TclEncodingProfiles { +static const struct TclEncodingProfiles { const char *name; int value; } encodingProfiles[] = { @@ -201,10 +201,10 @@ static struct TclEncodingProfiles { {"tcl8", TCL_ENCODING_PROFILE_TCL8}, }; #define PROFILE_STRICT(flags_) \ - (ENCODING_PROFILE_GET(flags_) == TCL_ENCODING_PROFILE_STRICT) + ((flags_) & TCL_ENCODING_PROFILE_STRICT) #define PROFILE_REPLACE(flags_) \ - (ENCODING_PROFILE_GET(flags_) == TCL_ENCODING_PROFILE_REPLACE) + ((ENCODING_PROFILE_GET(flags_) == TCL_ENCODING_PROFILE_REPLACE) && !PROFILE_STRICT(flags_)) #define UNICODE_REPLACE_CHAR ((Tcl_UniChar)0xFFFD) #define SURROGATE(c_) (((c_) & ~0x7FF) == 0xD800) @@ -439,6 +439,7 @@ FillEncodingFileMap(void) Tcl_Size i, numDirs = 0; Tcl_Obj *map, *searchPath; +fprintf(stdout, "FEFM CALLER\n"); fflush(stdout); searchPath = Tcl_GetEncodingSearchPath(); Tcl_IncrRefCount(searchPath); TclListObjLengthM(NULL, searchPath, &numDirs); @@ -722,6 +723,7 @@ Tcl_GetDefaultEncodingDir(void) { int numDirs; Tcl_Obj *first, *searchPath = Tcl_GetEncodingSearchPath(); +fprintf(stdout, "GDE CALLER\n"); fflush(stdout); TclListObjLengthM(NULL, searchPath, &numDirs); if (numDirs == 0) { @@ -758,6 +760,7 @@ Tcl_SetDefaultEncodingDir( searchPath = Tcl_DuplicateObj(searchPath); Tcl_ListObjReplace(NULL, searchPath, 0, 0, 1, &directory); +fprintf(stdout, "SDE CALLER\n"); fflush(stdout); Tcl_SetEncodingSearchPath(searchPath); } #endif @@ -1183,10 +1186,6 @@ Tcl_ExternalToUtfDString( * Possible flags values: * target encoding. It should be composed by OR-ing the following: * - *At most one* of TCL_ENCODING_PROFILE{DEFAULT,TCL8,STRICT} - * - TCL_ENCODING_STOPONERROR: Backward compatibility. Sets the profile - * to TCL_ENCODING_PROFILE_STRICT overriding any specified profile flags - * Any other flag bits will cause an error to be returned (for future - * compatibility) * * Results: * The return value is one of @@ -1489,8 +1488,6 @@ Tcl_UtfToExternalDString( * the source buffer are invalid or cannot be represented in the * target encoding. It should be composed by OR-ing the following: * - *At most one* of TCL_ENCODING_PROFILE_* - * - TCL_ENCODING_STOPONERROR: Backward compatibility. Sets the profile - * to TCL_ENCODING_PROFILE_STRICT overriding any specified profile flags * * Results: * The return value is one of @@ -1778,6 +1775,7 @@ OpenEncodingFileChannel( Tcl_Obj **dir, *path, *directory = NULL; Tcl_Channel chan = NULL; Tcl_Size i, numDirs; +fprintf(stdout, "OEFC CALLER\n"); fflush(stdout); TclListObjGetElementsM(NULL, searchPath, &numDirs, &dir); Tcl_IncrRefCount(nameObj); @@ -2413,7 +2411,6 @@ BinaryProc( if (dstLen < 0) { dstLen = 0; } - flags = TclEncodingSetProfileFlags(flags); if ((flags & TCL_ENCODING_CHAR_LIMIT) && srcLen > *dstCharsPtr) { srcLen = *dstCharsPtr; } @@ -2481,7 +2478,6 @@ UtfToUtfProc( srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; - flags = TclEncodingSetProfileFlags(flags); if ((flags & TCL_ENCODING_END) == 0) { srcClose -= 6; } @@ -2697,7 +2693,6 @@ Utf32ToUtfProc( int result, numChars, charLimit = INT_MAX; int ch = 0, bytesLeft = srcLen % 4; - flags = TclEncodingSetProfileFlags(flags); flags |= PTR2INT(clientData); if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; @@ -2854,7 +2849,6 @@ UtfToUtf32Proc( srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; - flags = TclEncodingSetProfileFlags(flags); if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } @@ -2952,7 +2946,6 @@ Utf16ToUtfProc( int result, numChars, charLimit = INT_MAX; unsigned short ch = 0; - flags = TclEncodingSetProfileFlags(flags); flags |= PTR2INT(clientData); if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; @@ -3113,7 +3106,6 @@ UtfToUtf16Proc( srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; - flags = TclEncodingSetProfileFlags(flags); if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } @@ -3219,7 +3211,6 @@ UtfToUcs2Proc( int result, numChars, len; Tcl_UniChar ch = 0; - flags = TclEncodingSetProfileFlags(flags); flags |= PTR2INT(clientData); srcStart = src; srcEnd = src + srcLen; @@ -3329,7 +3320,6 @@ TableToUtfProc( const unsigned short *pageZero; TableEncodingData *dataPtr = (TableEncodingData *)clientData; - flags = TclEncodingSetProfileFlags(flags); if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } @@ -3462,7 +3452,6 @@ TableFromUtfProc( srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; - flags = TclEncodingSetProfileFlags(flags); if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } @@ -3563,7 +3552,6 @@ Iso88591ToUtfProc( const char *dstEnd, *dstStart; int result, numChars, charLimit = INT_MAX; - flags = TclEncodingSetProfileFlags(flags); if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } @@ -3648,7 +3636,6 @@ Iso88591FromUtfProc( srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; - flags = TclEncodingSetProfileFlags(flags); if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } @@ -3788,7 +3775,6 @@ EscapeToUtfProc( int state, result, numChars, charLimit = INT_MAX; const char *dstStart, *dstEnd; - flags = TclEncodingSetProfileFlags(flags); if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } @@ -4010,7 +3996,6 @@ EscapeFromUtfProc( srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; - flags = TclEncodingSetProfileFlags(flags); if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } @@ -4332,6 +4317,8 @@ InitializeEncodingSearchPath( Tcl_Size i, numDirs, numBytes; Tcl_Obj *libPathObj, *encodingObj, *searchPathObj; +fprintf(stdout, "IESP\n"); fflush(stdout); + TclNewLiteralStringObj(encodingObj, "encoding"); TclNewObj(searchPathObj); Tcl_IncrRefCount(encodingObj); @@ -4459,48 +4446,6 @@ TclEncodingProfileIdToName( /* *------------------------------------------------------------------------ * - * TclEncodingSetProfileFlags -- - * - * Maps the flags supported in the encoding C API's to internal flags. - * - * For backward compatibility reasons, TCL_ENCODING_STOPONERROR is - * is mapped to the TCL_ENCODING_PROFILE_STRICT overwriting any profile - * specified. - * - * If no profile or an invalid profile is specified, it is set to - * the default. - * - * Results: - * Internal encoding flag mask. - * - * Side effects: - * None. - * - *------------------------------------------------------------------------ - */ -int TclEncodingSetProfileFlags(int flags) -{ - if (flags & TCL_ENCODING_STOPONERROR) { - ENCODING_PROFILE_SET(flags, TCL_ENCODING_PROFILE_STRICT); - } else { - int profile = ENCODING_PROFILE_GET(flags); - switch (profile) { - case TCL_ENCODING_PROFILE_TCL8: - case TCL_ENCODING_PROFILE_STRICT: - case TCL_ENCODING_PROFILE_REPLACE: - break; - case 0: /* Unspecified by caller */ - default: - ENCODING_PROFILE_SET(flags, TCL_ENCODING_PROFILE_TCL8); - break; - } - } - return flags; -} - -/* - *------------------------------------------------------------------------ - * * TclGetEncodingProfiles -- * * Get the list of supported encoding profiles. diff --git a/generic/tclFileName.c b/generic/tclFileName.c index 92d325f..7f4f1cc 100644 --- a/generic/tclFileName.c +++ b/generic/tclFileName.c @@ -2145,7 +2145,7 @@ DoGlob( Tcl_GlobTypeData *types) /* List object containing list of acceptable * types. May be NULL. */ { - int baseLength, quoted, count; + int baseLength, quoted; int result = TCL_OK; char *name, *p, *openBrace, *closeBrace, *firstSpecialChar; Tcl_Obj *joinedPtr; @@ -2155,7 +2155,6 @@ DoGlob( * past the last initial separator. */ - count = 0; name = pattern; for (; *pattern != '\0'; pattern++) { if (*pattern == '\\') { @@ -2175,7 +2174,6 @@ DoGlob( } else if (strchr(separators, *pattern) == NULL) { break; } - count++; } /* diff --git a/generic/tclGetDate.y b/generic/tclGetDate.y index 13daef2..5a79cf2 100644 --- a/generic/tclGetDate.y +++ b/generic/tclGetDate.y @@ -641,7 +641,7 @@ static const TABLE TimezoneTable[] = { { "ist", tZONE, -HOUR(11/2) }, /* Indian Standard */ { "zp6", tZONE, -HOUR( 6) }, /* USSR Zone 5 */ #if 0 - /* For completeness. NST is also Newfoundland Stanard, nad SST is + /* For completeness. NST is also Newfoundland Standard, and SST is * also Swedish Summer. */ { "nst", tZONE, -HOUR(13/2) }, /* North Sumatra */ { "sst", tZONE, -HOUR( 7) }, /* South Sumatra, USSR Zone 6 */ @@ -1099,7 +1099,7 @@ TclClockOldscanObjCmd( } Tcl_ListObjAppendElement(interp, result, resultElement); - TcNewObj(resultElement); + TclNewObj(resultElement); if (yyHaveDay && !yyHaveDate) { Tcl_ListObjAppendElement(interp, resultElement, Tcl_NewIntObj((int) yyDayOrdinal)); diff --git a/generic/tclIO.c b/generic/tclIO.c index 6461909..bc1b1c6 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -4749,6 +4749,12 @@ Tcl_GetsObj( ResetFlag(statePtr, CHANNEL_BLOCKED); while (1) { if (dst >= dstEnd) { + /* + * In case of encoding errors, state gets flag + * CHANNEL_ENCODING_ERROR set in the call below. First, the + * EOF/EOL condition is checked, as we may have valid data with + * EOF/EOL before the encoding error. + */ if (FilterInputBytes(chanPtr, &gs) != 0) { goto restore; } @@ -4918,8 +4924,17 @@ Tcl_GetsObj( } goto gotEOL; } else if (gs.bytesWrote == 0 - && GotFlag(statePtr, CHANNEL_ENCODING_ERROR) - && !GotFlag(statePtr, CHANNEL_NONBLOCKING)) { + && GotFlag(statePtr, CHANNEL_ENCODING_ERROR)) { + /* Ticket c4eb46a1 Harald Oehlmann 2023-11-12 debugging session. + * In non blocking mode we loop indifenitly on a decoding error in + * this while-loop. + * Removed the following from the upper condition: + * "&& !GotFlag(statePtr, CHANNEL_NONBLOCKING)" + * In case of an encoding error with leading correct bytes, we pass here + * two times, as gs.bytesWrote is not 0 on the first pass. This feels + * once to much, as the data is anyway not used. + */ + /* Set eol to the position that caused the encoding error, and then * continue to gotEOL, which stores the data that was decoded * without error to objPtr. This allows the caller to do something diff --git a/generic/tclInt.h b/generic/tclInt.h index 0b46184..3d8a702 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2886,11 +2886,13 @@ typedef struct ProcessGlobalValue { */ #define ENCODING_PROFILE_MASK 0xFF000000 -#define ENCODING_PROFILE_GET(flags_) ((flags_) & ENCODING_PROFILE_MASK) -#define ENCODING_PROFILE_SET(flags_, profile_) \ - do { \ - (flags_) &= ~ENCODING_PROFILE_MASK; \ - (flags_) |= profile_; \ +#define ENCODING_PROFILE_GET(flags_) (((flags_) & TCL_ENCODING_PROFILE_STRICT) ? \ + TCL_ENCODING_PROFILE_STRICT : (((flags_) & ENCODING_PROFILE_MASK) ? \ + ((flags_) & ENCODING_PROFILE_MASK) : TCL_ENCODING_PROFILE_TCL8)) +#define ENCODING_PROFILE_SET(flags_, profile_) \ + do { \ + (flags_) &= ~(ENCODING_PROFILE_MASK|TCL_ENCODING_PROFILE_STRICT); \ + (flags_) |= (profile_) & (ENCODING_PROFILE_MASK|TCL_ENCODING_PROFILE_STRICT); \ } while (0) /* @@ -2916,7 +2918,6 @@ TclEncodingProfileNameToId(Tcl_Interp *interp, int *profilePtr); MODULE_SCOPE const char *TclEncodingProfileIdToName(Tcl_Interp *interp, int profileId); -MODULE_SCOPE int TclEncodingSetProfileFlags(int flags); MODULE_SCOPE void TclGetEncodingProfiles(Tcl_Interp *interp); /* diff --git a/generic/tclInterp.c b/generic/tclInterp.c index ed3c527..0325091 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -393,20 +393,25 @@ Tcl_Init( " rename tclInit {}\n" " if {[info exists tcl_library]} {\n" " set scripts {{set tcl_library}}\n" +"puts A-SCRIPTS:$scripts\n" " } else {\n" " set scripts {}\n" " if {[info exists env(TCL_LIBRARY)] && ($env(TCL_LIBRARY) ne {})} {\n" " lappend scripts {set env(TCL_LIBRARY)}\n" +"puts B-SCRIPTS:$scripts\n" " lappend scripts {\n" "if {[regexp ^tcl(.*)$ [file tail $env(TCL_LIBRARY)] -> tail] == 0} continue\n" "if {$tail eq [info tclversion]} continue\n" "file join [file dirname $env(TCL_LIBRARY)] tcl[info tclversion]}\n" +"puts C-SCRIPTS:$scripts\n" " }\n" -" lappend scripts {::tcl::zipfs::tcl_library_init}\n" +"puts D-SCRIPTS:$scripts\n" " if {[info exists tclDefaultLibrary]} {\n" " lappend scripts {set tclDefaultLibrary}\n" +"puts E-SCRIPTS:$scripts\n" " } else {\n" " lappend scripts {::tcl::pkgconfig get scriptdir,runtime}\n" +"puts F-SCRIPTS:$scripts\n" " }\n" " lappend scripts {\n" "set parentDir [file dirname [file dirname [info nameofexecutable]]]\n" @@ -425,12 +430,14 @@ Tcl_Init( " lappend scripts [list lindex \\$tcl_libPath $i]\n" " }\n" " }\n" +"puts G-SCRIPTS:$scripts\n" " }\n" " set dirs {}\n" " set errors {}\n" " foreach script $scripts {\n" " lappend dirs [eval $script]\n" " set tcl_library [lindex $dirs end]\n" +"puts TRIAL:$tcl_library\n" " set tclfile [file join $tcl_library init.tcl]\n" " if {[file exists $tclfile]} {\n" " if {[catch {uplevel #0 [list source $tclfile]} msg opts]} {\n" diff --git a/generic/tclNamesp.c b/generic/tclNamesp.c index 24d9646..7a32fd9 100644 --- a/generic/tclNamesp.c +++ b/generic/tclNamesp.c @@ -2156,7 +2156,7 @@ DeleteImportedCmd( * If "flags" contains TCL_CREATE_NS_IF_UNKNOWN, all namespace components * of the qualified name that cannot be found are automatically created * within their specified parent. This makes sure that functions like - * Tcl_CreateCommand always succeed. There is no alternate search path, + * Tcl_CreateObjCommand always succeed. There is no alternate search path, * so *altNsPtrPtr is set NULL. * * If "flags" contains TCL_FIND_ONLY_NS, the qualified name is treated as @@ -2353,7 +2353,7 @@ TclGetNamespaceForQualName( * Look up the namespace qualifier nsName in the current namespace * context. If it isn't found but TCL_CREATE_NS_IF_UNKNOWN is set, * create that qualifying namespace. This is needed for functions like - * Tcl_CreateCommand that cannot fail. + * Tcl_CreateObjCommand that cannot fail. */ if (nsPtr != NULL) { diff --git a/generic/tclTest.c b/generic/tclTest.c index 895de64..efa045e 100644 --- a/generic/tclTest.c +++ b/generic/tclTest.c @@ -2086,7 +2086,7 @@ static int UtfExtWrapper( } flagMap[] = { {"start", TCL_ENCODING_START}, {"end", TCL_ENCODING_END}, - {"stoponerror", TCL_ENCODING_STOPONERROR}, + {"stoponerror", TCL_ENCODING_PROFILE_STRICT}, {"noterminate", TCL_ENCODING_NO_TERMINATE}, {"charlimit", TCL_ENCODING_CHAR_LIMIT}, {"profiletcl8", TCL_ENCODING_PROFILE_TCL8}, diff --git a/generic/tclZipfs.c b/generic/tclZipfs.c index 842d51a..5400f92 100644 --- a/generic/tclZipfs.c +++ b/generic/tclZipfs.c @@ -91,6 +91,17 @@ static const z_crc_t* crc32tab; #define ZIPFS_VOLUME_LEN 9 #define ZIPFS_APP_MOUNT ZIPFS_VOLUME "app" #define ZIPFS_ZIP_MOUNT ZIPFS_VOLUME "lib/tcl" + +#define ZIPFS_SCRIPT_PREFIX "set ::tcl_library " +#define ZIPFS_TCL_LIBRARY_1 ZIPFS_APP_MOUNT "/tcl_library" +#define ZIPFS_INIT_SCRIPT_1 ZIPFS_SCRIPT_PREFIX ZIPFS_TCL_LIBRARY_1 + +#define ZIPFS_TCL_LIBRARY_2 ZIPFS_ZIP_MOUNT +#define ZIPFS_INIT_SCRIPT_2 ZIPFS_SCRIPT_PREFIX ZIPFS_TCL_LIBRARY_2 + +#define ZIPFS_TCL_LIBRARY_3 ZIPFS_ZIP_MOUNT "/tcl_library" +#define ZIPFS_INIT_SCRIPT_3 ZIPFS_SCRIPT_PREFIX ZIPFS_TCL_LIBRARY_3 + #define ZIPFS_FALLBACK_ENCODING "cp437" /* @@ -313,6 +324,7 @@ static const char pwrot[17] = "\x10\x90\x50\xD0\x30\xB0\x70\xF0"; static const char *zipfs_literal_tcl_library = NULL; +static const char *zipfs_init_script = NULL; /* Function prototypes */ @@ -906,7 +918,7 @@ DecodeZipEntryText( dst = Tcl_DStringValue(dstPtr); dstLen = dstPtr->spaceAvl - 1; flags = TCL_ENCODING_START | TCL_ENCODING_END | - TCL_ENCODING_STOPONERROR; /* Special flag! */ + TCL_ENCODING_PROFILE_STRICT; /* Special flag! */ while (1) { int srcRead, dstWrote; @@ -4231,6 +4243,28 @@ ScriptLibrarySetup( Tcl_Obj *libDirObj = Tcl_NewStringObj(dirName, -1); Tcl_Obj *subDirObj, *searchPathObj; + /* + * We know where the init.tcl is located in the attached script library + * archive. Use a pre-init script to tell every Tcl interp as it gets + * created where that is, so none of them need to construct and then + * iterate through some search path. That's the literal documented + * purpose of Tcl_SetPreInitScript(). Use it. + * + * TODO: Examine why we need so many variations and eliminate as many + * as possible. + */ + + if (0 == strcmp(zipfs_literal_tcl_library, ZIPFS_TCL_LIBRARY_1)) { + zipfs_init_script = ZIPFS_INIT_SCRIPT_1; + } else if (0 == strcmp(zipfs_literal_tcl_library, ZIPFS_TCL_LIBRARY_2)) { + zipfs_init_script = ZIPFS_INIT_SCRIPT_2; + } else if (0 == strcmp(zipfs_literal_tcl_library, ZIPFS_TCL_LIBRARY_3)) { + zipfs_init_script = ZIPFS_INIT_SCRIPT_3; + } + if (zipfs_init_script) { + Tcl_SetPreInitScript(zipfs_init_script); + } + TclNewLiteralStringObj(subDirObj, "encoding"); Tcl_IncrRefCount(subDirObj); TclNewObj(searchPathObj); @@ -4238,6 +4272,7 @@ ScriptLibrarySetup( Tcl_FSJoinToPath(libDirObj, 1, &subDirObj)); Tcl_DecrRefCount(subDirObj); Tcl_IncrRefCount(searchPathObj); +fprintf(stdout, "AH CALLER\n"); fflush(stdout); Tcl_SetEncodingSearchPath(searchPathObj); Tcl_DecrRefCount(searchPathObj); return libDirObj; @@ -4268,13 +4303,12 @@ TclZipfs_TclLibrary(void) * Look for the library file system within the executable. */ - vfsInitScript = Tcl_NewStringObj(ZIPFS_APP_MOUNT "/tcl_library/init.tcl", - -1); + vfsInitScript = Tcl_NewStringObj(ZIPFS_TCL_LIBRARY_1 "/init.tcl", -1); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == TCL_OK) { - zipfs_literal_tcl_library = ZIPFS_APP_MOUNT "/tcl_library"; + zipfs_literal_tcl_library = ZIPFS_TCL_LIBRARY_1; return ScriptLibrarySetup(zipfs_literal_tcl_library); } @@ -4323,44 +4357,6 @@ TclZipfs_TclLibrary(void) /* *------------------------------------------------------------------------- * - * ZipFSTclLibraryObjCmd -- - * - * This procedure is invoked to process the - * [::tcl::zipfs::tcl_library_init] command, usually called during the - * execution of Tcl's interpreter startup. It returns the root that Tcl's - * library files are mounted under. - * - * Results: - * A standard Tcl result. - * - * Side effects: - * May initialise the cache of where such library files are to be found. - * This cache is never cleared. - * - *------------------------------------------------------------------------- - */ - -static int -ZipFSTclLibraryObjCmd( - TCL_UNUSED(void *), - Tcl_Interp *interp, /* Current interpreter. */ - TCL_UNUSED(int) /*objc*/, - TCL_UNUSED(Tcl_Obj *const *)) /*objv*/ -{ - if (!Tcl_IsSafe(interp)) { - Tcl_Obj *pResult = TclZipfs_TclLibrary(); - - if (!pResult) { - TclNewObj(pResult); - } - Tcl_SetObjResult(interp, pResult); - } - return TCL_OK; -} - -/* - *------------------------------------------------------------------------- - * * ZipChannelClose -- * * This function is called to close a channel. @@ -6253,8 +6249,6 @@ TclZipfs_Init( Tcl_GetEnsembleMappingDict(NULL, ensemble, &mapObj); Tcl_DictObjPut(NULL, mapObj, Tcl_NewStringObj("find", -1), Tcl_NewStringObj("::tcl::zipfs::find", -1)); - Tcl_CreateObjCommand(interp, "::tcl::zipfs::tcl_library_init", - ZipFSTclLibraryObjCmd, NULL, NULL); Tcl_PkgProvide(interp, "tcl::zipfs", "2.0"); } return TCL_OK; @@ -6283,22 +6277,21 @@ ZipfsAppHookFindTclInit( return TCL_ERROR; } - TclNewLiteralStringObj(vfsInitScript, ZIPFS_ZIP_MOUNT "/init.tcl"); + TclNewLiteralStringObj(vfsInitScript, ZIPFS_TCL_LIBRARY_2 "/init.tcl"); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == 0) { - zipfs_literal_tcl_library = ZIPFS_ZIP_MOUNT; + zipfs_literal_tcl_library = ZIPFS_TCL_LIBRARY_2; return TCL_OK; } - TclNewLiteralStringObj(vfsInitScript, - ZIPFS_ZIP_MOUNT "/tcl_library/init.tcl"); + TclNewLiteralStringObj(vfsInitScript, ZIPFS_TCL_LIBRARY_3 "/init.tcl"); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == 0) { - zipfs_literal_tcl_library = ZIPFS_ZIP_MOUNT "/tcl_library"; + zipfs_literal_tcl_library = ZIPFS_TCL_LIBRARY_3; return TCL_OK; } @@ -6415,12 +6408,13 @@ TclZipfs_AppHook( if (!zipfs_literal_tcl_library) { TclNewLiteralStringObj(vfsInitScript, - ZIPFS_APP_MOUNT "/tcl_library/init.tcl"); + ZIPFS_TCL_LIBRARY_1 "/init.tcl"); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == TCL_OK) { - zipfs_literal_tcl_library = ZIPFS_APP_MOUNT "/tcl_library"; + zipfs_literal_tcl_library = ZIPFS_TCL_LIBRARY_1; + Tcl_DecrRefCount(TclZipfs_TclLibrary()); return version; } } @@ -6447,9 +6441,9 @@ TclZipfs_AppHook( * wants it. */ - TclZipfs_TclLibrary(); + Tcl_DecrRefCount(TclZipfs_TclLibrary()); TclNewLiteralStringObj(vfsInitScript, - ZIPFS_ZIP_MOUNT "/tcl_library/install.tcl"); + ZIPFS_TCL_LIBRARY_3 "install.tcl"); Tcl_IncrRefCount(vfsInitScript); if (Tcl_FSAccess(vfsInitScript, F_OK) == 0) { Tcl_SetStartupScript(vfsInitScript, NULL); @@ -6459,6 +6453,17 @@ TclZipfs_AppHook( int found; Tcl_Obj *vfsInitScript; + /* Set Tcl Encodings */ + TclNewLiteralStringObj(vfsInitScript, + ZIPFS_TCL_LIBRARY_1 "/init.tcl"); + Tcl_IncrRefCount(vfsInitScript); + found = Tcl_FSAccess(vfsInitScript, F_OK); + Tcl_DecrRefCount(vfsInitScript); + if (found == TCL_OK) { + zipfs_literal_tcl_library = ZIPFS_TCL_LIBRARY_1; + Tcl_DecrRefCount(TclZipfs_TclLibrary()); + } + TclNewLiteralStringObj(vfsInitScript, ZIPFS_APP_MOUNT "/main.tcl"); Tcl_IncrRefCount(vfsInitScript); if (Tcl_FSAccess(vfsInitScript, F_OK) == 0) { @@ -6470,14 +6475,8 @@ TclZipfs_AppHook( } else { Tcl_DecrRefCount(vfsInitScript); } - /* Set Tcl Encodings */ - TclNewLiteralStringObj(vfsInitScript, - ZIPFS_APP_MOUNT "/tcl_library/init.tcl"); - Tcl_IncrRefCount(vfsInitScript); - found = Tcl_FSAccess(vfsInitScript, F_OK); - Tcl_DecrRefCount(vfsInitScript); + if (found == TCL_OK) { - zipfs_literal_tcl_library = ZIPFS_APP_MOUNT "/tcl_library"; return version; } } diff --git a/tests/io.test b/tests/io.test index 9f731ad..7e62e6b 100644 --- a/tests/io.test +++ b/tests/io.test @@ -9193,7 +9193,7 @@ test io-75.5 {invalid utf-8 encoding read is ignored (-profile tcl8)} -setup { removeFile io-75.5 } -result 4181 -test io-75.6 {invalid utf-8 encoding, gets is not ignored (-profile strict)} -setup { +test io-75.6 {incomplete utf-8 encoding, blocking gets is not ignored (-profile strict)} -setup { set fn [makeFile {} io-75.6] set f [open $fn w+] fconfigure $f -encoding binary @@ -9211,6 +9211,84 @@ test io-75.6 {invalid utf-8 encoding, gets is not ignored (-profile strict)} -se } -match glob -returnCodes 1 -result {error reading "file*":\ invalid or incomplete multibyte or wide character} +test io-75.6.1 {invalid utf-8 encoding, blocking gets is not ignored (-profile strict)} -setup { + set fn [makeFile {} io-75.6.1] + set f [open $fn w+] + fconfigure $f -encoding binary + # utf-8: \xC3 requires a 2nd byte > x80, but <x80 is delivered + puts -nonewline $f A\xC3B + flush $f + seek $f 0 + fconfigure $f -encoding utf-8 -buffering none -eofchar {} \ + -translation lf -profile strict +} -body { + gets $f +} -cleanup { + close $f + removeFile io-75.6.1 +} -match glob -returnCodes 1 -result {error reading "file*":\ + invalid or incomplete multibyte or wide character} + +test io-75.6.2 {invalid utf-8 encoding, blocking gets is not ignored (-profile strict), recover functionality} -setup { + set fn [makeFile {} io-75.6.2] + set f [open $fn w+] + fconfigure $f -encoding binary + # utf-8: \xC3 requires a 2nd byte > x80, but <x80 is delivered + puts -nonewline $f A\xC3B + flush $f + seek $f 0 + fconfigure $f -encoding utf-8 -buffering none -eofchar {} \ + -translation lf -profile strict +} -body { + set l {} + lappend l [catch {gets $f}] + lappend l [tell $f] + fconfigure $f -encoding binary + lappend l [expr {[gets $f] eq "A\xC3B"}] +} -cleanup { + close $f + removeFile io-75.6.2 +} -match glob -returnCodes 0 -result {1 0 1} + +# TCL ticket c4eb46a196: non blocking case had endless loop, so test it +test io-75.6.3 {invalid utf-8 encoding, non blocking gets is not ignored (-profile strict)} -setup { + set fn [makeFile {} io-75.6.3] + set f [open $fn w+] + fconfigure $f -encoding binary + # utf-8: \xC3 requires a 2nd byte > x80, but <x80 is delivered + puts -nonewline $f A\xC3B + flush $f + seek $f 0 + fconfigure $f -encoding utf-8 -buffering none -eofchar {} \ + -translation lf -profile strict -blocking 0 +} -body { + gets $f +} -cleanup { + close $f + removeFile io-75.6.3 +} -match glob -returnCodes 1 -result {error reading "file*":\ + invalid or incomplete multibyte or wide character} + +test io-75.6.4 {incomplete utf-8 encoding, non blocking gets is not ignored (-profile strict)} -setup { + set fn [makeFile {} io-75.6.4] + set f [open $fn w+] + fconfigure $f -encoding binary + # \x81 is an incomplete byte sequence in utf-8 + puts -nonewline $f A\x81 + flush $f + seek $f 0 + fconfigure $f -encoding utf-8 -buffering none -eofchar {} \ + -translation lf -profile strict -blocking 0 +} -body { + gets $f + # only the 2nd gets returns the error + gets $f +} -cleanup { + close $f + removeFile io-75.6.4 +} -match glob -returnCodes 1 -result {error reading "file*":\ + invalid or incomplete multibyte or wide character} + test io-75.7 { invalid utf-8 encoding read is not ignored (-profile strict) } -setup { @@ -9232,7 +9310,7 @@ test io-75.7 { } -match glob -result {1 {error reading "file*":\ invalid or incomplete multibyte or wide character}} -test io-75.8 {invalid utf-8 encoding eof handling (-profile strict)} -setup { +test io-75.8 {invalid utf-8 encoding eof first handling (-profile strict)} -setup { set fn [makeFile {} io-75.8] set f [open $fn w+] fconfigure $f -encoding binary @@ -9254,7 +9332,7 @@ test io-75.8 {invalid utf-8 encoding eof handling (-profile strict)} -setup { removeFile io-75.8 } -result {41 1 {}} -test io-75.8.eoflater {invalid utf-8 encoding eof handling (-profile strict)} -setup { +test io-75.8.eoflater {invalid utf-8 encoding eof after handling (-profile strict)} -setup { set fn [makeFile {} io-75.8] set f [open $fn w+] # This also configures the channel encoding profile as strict. diff --git a/unix/Makefile.in b/unix/Makefile.in index 29a28eb..a081bb8 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -2106,6 +2106,8 @@ dist-packages: configure-packages # the name of the .y file so that make doesn't try to automatically regenerate # the .c file. +# +# Remark: see [54a305cb88]. tclDate.c is manually edited, removing the unused "yynerrs" variable gendate: bison --output-file=$(GENERIC_DIR)/tclDate.c \ --no-lines \ diff --git a/unix/tclAppInit.c b/unix/tclAppInit.c index b203487..04ae564 100644 --- a/unix/tclAppInit.c +++ b/unix/tclAppInit.c @@ -148,7 +148,7 @@ Tcl_AppInit( */ /* - * Call Tcl_CreateCommand for application-specific commands, if they + * Call Tcl_CreateObjCommand for application-specific commands, if they * weren't already created by the init procedures called above. */ diff --git a/win/tclAppInit.c b/win/tclAppInit.c index a3914f1..d1b38ee 100644 --- a/win/tclAppInit.c +++ b/win/tclAppInit.c @@ -204,7 +204,7 @@ Tcl_AppInit( */ /* - * Call Tcl_CreateCommand for application-specific commands, if they + * Call Tcl_CreateObjCommand for application-specific commands, if they * weren't already created by the init procedures called above. */ |
