From ee6b2e34559aa9915b480794418f1db489d723a8 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 29 Aug 2019 20:28:47 +0000 Subject: add test cases covering nested compilation bug [fec0c17d39] (8.5 is not affected at the moment by nested count under 2500) --- tests/compile.test | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/compile.test b/tests/compile.test index 7646c12..c9f1b71 100644 --- a/tests/compile.test +++ b/tests/compile.test @@ -422,6 +422,37 @@ test compile-13.1 {testing underestimate of maxStackSize in list cmd} {exec} { list [catch {exec [interpreter] << $script} msg] $msg } {0 OK} +# Tests of nested compile (body in body compilation), should not generate stack overflow +# (with abnormal program termination), bug [fec0c17d39]: +test compile-13.2 {TclCompileScript: testing nested scripts compilation} -setup { + set i [interp create] + interp recursionlimit $i [expr {10000+50}] + $i eval {proc gencode {nr {cmd eval} {nl 0}} { + set code "" + set e ""; if {$nl} {set e "\n"} + for {set i 0} {$i < $nr} {incr i} { + append code "$cmd \{$e" + } + append code "lappend result 1$e" + for {set i 0} {$i < $nr} {incr i} { + append code "\}$e" + } + #puts [format "%% %.40s ... %d bytes" $code [string length $code]] + return $code + }} +} -body { + # Test different compilation variants (instructions evalStk, invokeStk, etc), + # with 2000 nested scripts (bodies). If you get SO/SF exceptions on some low-stack + # boxes or systems, please don't decrease it (either provide a constraint) + $i eval {foreach cmd {eval "if 1" catch} { + set c [gencode 2000 $cmd] + if 1 $c + }} + $i eval {set result} +} -result {1 1 1} -cleanup { + interp delete $i +} + # Tests compile-14.* for [Bug 599788] [Bug 0c043a175a47da8c2342] test compile-14.1 {testing errors in element name; segfault?} {} { catch {set a([error])} msg1 -- cgit v0.12 From 87e3efddb07fb90ce44be9869ca883a896f1b122 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 29 Aug 2019 20:46:46 +0000 Subject: more variants in test (since 8.6 compiles "try" using evalStk instruction) --- tests/compile.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compile.test b/tests/compile.test index c651804..ee95d25 100644 --- a/tests/compile.test +++ b/tests/compile.test @@ -488,12 +488,12 @@ test compile-13.2 {TclCompileScript: testing nested scripts compilation} -setup # Test different compilation variants (instructions evalStk, invokeStk, etc), # with 2000 nested scripts (bodies). If you get SO/SF exceptions on some low-stack # boxes or systems, please don't decrease it (either provide a constraint) - $i eval {foreach cmd {eval "if 1" catch} { + $i eval {foreach cmd {eval "if 1" try catch} { set c [gencode 2000 $cmd] if 1 $c }} $i eval {set result} -} -result {1 1 1} -cleanup { +} -result {1 1 1 1} -cleanup { interp delete $i } -- cgit v0.12 From 7efc804258d3dcc195a86386704ed09e7691d9fe Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 29 Aug 2019 20:50:31 +0000 Subject: closes [fec0c17d39]: fixed stack overflow (followed by SF) by compilation of too many nested bodies (don't use system stack, size of Tcl_Parse is ca. 400 bytes and compiler proc's of commands are reserving stack too) --- generic/tclCompile.c | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/generic/tclCompile.c b/generic/tclCompile.c index 6f90072..87f1bfc 100644 --- a/generic/tclCompile.c +++ b/generic/tclCompile.c @@ -2128,18 +2128,26 @@ TclCompileScript( /* Each iteration compiles one command from the script. */ - while (numBytes > 0) { - Tcl_Parse parse; + if (numBytes > 0) { + /* + * Don't use system stack (size of Tcl_Parse is ca. 400 bytes), so + * many nested compilations (body enclosed in body) can cause abnormal + * program termination with a stack overflow exception, bug [fec0c17d39]. + */ + Tcl_Parse *parsePtr = ckalloc(sizeof(Tcl_Parse)); + + do { const char *next; - if (TCL_OK != Tcl_ParseCommand(interp, p, numBytes, 0, &parse)) { + if (TCL_OK != Tcl_ParseCommand(interp, p, numBytes, 0, parsePtr)) { /* - * Compile bytecodes to report the parse error at runtime. + * Compile bytecodes to report the parsePtr error at runtime. */ - Tcl_LogCommandInfo(interp, script, parse.commandStart, - parse.term + 1 - parse.commandStart); + Tcl_LogCommandInfo(interp, script, parsePtr->commandStart, + parsePtr->term + 1 - parsePtr->commandStart); TclCompileSyntaxError(interp, envPtr); + ckfree(parsePtr); return; } @@ -2150,9 +2158,9 @@ TclCompileScript( */ if ((tclTraceCompile >= 1) && (envPtr->procPtr == NULL)) { - int commandLength = parse.term - parse.commandStart; + int commandLength = parsePtr->term - parsePtr->commandStart; fprintf(stdout, " Compiling: "); - TclPrintSource(stdout, parse.commandStart, + TclPrintSource(stdout, parsePtr->commandStart, TclMin(commandLength, 55)); fprintf(stdout, "\n"); } @@ -2163,48 +2171,51 @@ TclCompileScript( * (See test info-30.33). */ - TclAdvanceLines(&envPtr->line, p, parse.commandStart); + TclAdvanceLines(&envPtr->line, p, parsePtr->commandStart); TclAdvanceContinuations(&envPtr->line, &envPtr->clNext, - parse.commandStart - envPtr->source); + parsePtr->commandStart - envPtr->source); /* * Advance parser to the next command in the script. */ - next = parse.commandStart + parse.commandSize; + next = parsePtr->commandStart + parsePtr->commandSize; numBytes -= next - p; p = next; - if (parse.numWords == 0) { + if (parsePtr->numWords == 0) { /* * The "command" parsed has no words. In this case we can skip * the rest of the loop body. With no words, clearly * CompileCommandTokens() has nothing to do. Since the parser * aggressively sucks up leading comment and white space, - * including newlines, parse.commandStart must be pointing at + * including newlines, parsePtr->commandStart must be pointing at * either the end of script, or a command-terminating semi-colon. * In either case, the TclAdvance*() calls have nothing to do. * Finally, when no words are parsed, no tokens have been - * allocated at parse.tokenPtr so there's also nothing for + * allocated at parsePtr->tokenPtr so there's also nothing for * Tcl_FreeParse() to do. * * The advantage of this shortcut is that CompileCommandTokens() - * can be written with an assumption that parse.numWords > 0, with + * can be written with an assumption that parsePtr->numWords > 0, with * the implication the CCT() always generates bytecode. */ continue; } - lastCmdIdx = CompileCommandTokens(interp, &parse, envPtr); + lastCmdIdx = CompileCommandTokens(interp, parsePtr, envPtr); /* * TIP #280: Track lines in the just compiled command. */ - TclAdvanceLines(&envPtr->line, parse.commandStart, p); + TclAdvanceLines(&envPtr->line, parsePtr->commandStart, p); TclAdvanceContinuations(&envPtr->line, &envPtr->clNext, p - envPtr->source); - Tcl_FreeParse(&parse); + Tcl_FreeParse(parsePtr); + } while (numBytes > 0); + + ckfree(parsePtr); } if (lastCmdIdx == -1) { -- cgit v0.12 From 6e297fef1f383d6ca3c7bf090b0351ad9f30aabc Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 30 Aug 2019 16:33:33 +0000 Subject: compile.test: reduce count of nested scripts to 1000 in debug case (seems to be to heavy on some platforms within debug-build); (small amend to the fix of [fec0c17d39]) --- tests/compile.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compile.test b/tests/compile.test index c9f1b71..c02acdb 100644 --- a/tests/compile.test +++ b/tests/compile.test @@ -442,10 +442,10 @@ test compile-13.2 {TclCompileScript: testing nested scripts compilation} -setup }} } -body { # Test different compilation variants (instructions evalStk, invokeStk, etc), - # with 2000 nested scripts (bodies). If you get SO/SF exceptions on some low-stack + # with 2000 (1000 in debug) nested scripts (bodies). If you get SO/SF exceptions on some low-stack # boxes or systems, please don't decrease it (either provide a constraint) $i eval {foreach cmd {eval "if 1" catch} { - set c [gencode 2000 $cmd] + set c [gencode [expr {![info exists ::tcl_platform(debug)] ? 2000 : 1000}] $cmd] if 1 $c }} $i eval {set result} -- cgit v0.12 From 7e14ee0b34c8b10709252eaf40b6201681bfb7f4 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 2 Sep 2019 08:28:39 +0000 Subject: Fix [5591e4a820]: @TCL_EXE@ not properly generated in 8.5 branch. Added @runstatedir@ in Makefile.in, not used yet (except if someone decides to re-generate "configure" with autconf-2.70). --- unix/configure | 2 +- win/Makefile.in | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/unix/configure b/unix/configure index 1e15a25..aedf8ff 100755 --- a/unix/configure +++ b/unix/configure @@ -18312,7 +18312,7 @@ echo "${ECHO_T}$tcl_ok" >&6 #------------------------------------------------------------------------ # Check whether the timezone data is supplied by the OS or has # to be installed by Tcl. The default is autodetection, but can -# be overriden on the configure command line either way. +# be overridden on the configure command line either way. #------------------------------------------------------------------------ echo "$as_me:$LINENO: checking for timezone data" >&5 diff --git a/win/Makefile.in b/win/Makefile.in index dbe8df2..6f2044f5 100644 --- a/win/Makefile.in +++ b/win/Makefile.in @@ -23,6 +23,7 @@ bindir = @bindir@ libdir = @libdir@ includedir = @includedir@ datarootdir = @datarootdir@ +runstatedir = @runstatedir@ mandir = @mandir@ # The following definition can be set to non-null for special systems like AFS @@ -163,7 +164,7 @@ MAN2TCL = man2tcl$(EXEEXT) # (make genstubs, make install) need it to be available on the PATH. This # executable should *NOT* be required just to do a normal build although # it can be required to run make dist. -TCL_EXE = @TCL_EXE@ +TCL_EXE = tclsh @SET_MAKE@ -- cgit v0.12 From 6f79cd18d73ff8505c57c11805d5476bc398e8ac Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 2 Sep 2019 11:07:46 +0000 Subject: Missing TCL_GLOBAL_ONLY flag in VwaitVarProc(): vwait always references global variables, this could lead to strange side-effects. --- generic/tclEvent.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 4db524c..0fed0a8 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1379,7 +1379,8 @@ VwaitVarProc( int *donePtr = (int *) clientData; *donePtr = 1; - Tcl_UntraceVar(interp, name1, TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + Tcl_UntraceVar2(interp, name1, name2, + TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, clientData); return NULL; } -- cgit v0.12 From dfb98f070561139ccf2d88b30fb134de3c3fcac4 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 2 Sep 2019 13:48:52 +0000 Subject: windows (mingw build): fix debug recognition (::tcl_platform(debug)), no debug if NDEBUG is set --- win/nmakehlp.c | 2 +- win/tclWinInit.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/win/nmakehlp.c b/win/nmakehlp.c index c21de63..fac32ee 100644 --- a/win/nmakehlp.c +++ b/win/nmakehlp.c @@ -643,7 +643,7 @@ SubstituteFile( } /* debug: dump the list */ -#ifdef _DEBUG +#ifndef NDEBUG { int n = 0; list_item_t *p = NULL; diff --git a/win/tclWinInit.c b/win/tclWinInit.c index 308d3f3..0574c37 100644 --- a/win/tclWinInit.c +++ b/win/tclWinInit.c @@ -602,7 +602,7 @@ TclpSetVariables( TCL_GLOBAL_ONLY); } -#ifdef _DEBUG +#ifndef NDEBUG /* * The existence of the "debug" element of the tcl_platform array * indicates that this particular Tcl shell has been compiled with debug -- cgit v0.12 From 07e8d24c838bb5f328de852deb361f3780d602fb Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 2 Sep 2019 14:13:33 +0000 Subject: Fix testing for debug build on UNIX too (on UNIX, ::tcl_platform(debug) is not set ....) --- tests/compile.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compile.test b/tests/compile.test index 548454b..3b91a5c 100644 --- a/tests/compile.test +++ b/tests/compile.test @@ -489,7 +489,7 @@ test compile-13.2 {TclCompileScript: testing nested scripts compilation} -setup # with 2000 (1000 in debug) nested scripts (bodies). If you get SO/SF exceptions on some low-stack # boxes or systems, please don't decrease it (either provide a constraint) $i eval {foreach cmd {eval "if 1" try catch} { - set c [gencode [expr {![info exists ::tcl_platform(debug)] ? 2000 : 1000}] $cmd] + set c [gencode [expr {![::tcl::pkgconfig get debug] ? 2000 : 1000}] $cmd] if 1 $c }} $i eval {set result} -- cgit v0.12 From f39babb15ad4c4c1131eb731e61417c7b68ac8cd Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Tue, 3 Sep 2019 10:48:52 +0000 Subject: Backout last commit: Looks like it causes test-failures in event.test on Windows. --- generic/tclEvent.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 0fed0a8..4db524c 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1379,8 +1379,7 @@ VwaitVarProc( int *donePtr = (int *) clientData; *donePtr = 1; - Tcl_UntraceVar2(interp, name1, name2, - TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + Tcl_UntraceVar(interp, name1, TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, clientData); return NULL; } -- cgit v0.12