Tcl Source Code

View Ticket
Login
Ticket UUID: a3309d01dbb0f23c3cf717ace30d9663fa54735
Title: Leak in array when unsetting keys from a proc
Type: Bug Version: 8.6.4
Submitter: bovine Created on: 2015-07-15 22:50:54
Subsystem: 07. Variables Assigned To: dgp
Priority: 5 Medium Severity: Severe
Status: Closed Last Modified: 2015-07-18 05:54:17
Resolution: Fixed Closed By: dgp
    Closed on: 2015-07-18 05:54:17
Description:
I've discovered a memory leak in Tcl 8.6.4 on FreeBSD 9.1 and 10.1 with the following script. The leak does not occur under Tcl 8.5

https://gist.github.com/bovine/15c350d044325a93d5cb -- attached

The issue seems to be specific to using lists that are built up using "lappend". If $foo is using a list that is directly "set" in a single operation, then the leak does not appear to occur.

Additionally, the leak occurs when using "unset" to remove individual elements from myarray. If you "array unset myarray" entirely, then the memory seems to be properly freed.
User Comments: dgp added on 2015-07-18 05:54:17:
Fix merged to trunk.

dgp added on 2015-07-17 19:34:00:
See branch bug-a3309d01db for conventional test that
demos the leak.  Branches off the checkin that bisecting
determines is the cause of the leak.

aspect added on 2015-07-16 03:22:29:
The following patch seems to fix this issue without breaking existing tests.  It's a bit brutish - a more sensitive patch would more closely compare this bytecoded INST_UNSET_ARRAY code with ArrayUnsetCmd().

Bug seems to have existed since 8.6.2, so I guess it's been in the bytecode all along.


Index: generic/tclExecute.c
==================================================================
--- generic/tclExecute.c
+++ generic/tclExecute.c
@@ -4178,11 +4178,11 @@
                 * No nasty traces and element exists, so we can proceed to
                 * unset it. Might still not exist though...
                 */
 
                if (!TclIsVarUndefined(varPtr)) {
-                   TclDecrRefCount(varPtr->value.objPtr);
+                   TclPtrUnsetVar(interp, varPtr, arrayPtr, NULL, part2Ptr, flags, opnd);
                } else if (flags & TCL_LEAVE_ERR_MSG) {
                    goto slowUnsetArray;
                }
                varPtr->value.objPtr = NULL;
                TRACE_APPEND(("OK\n"));

aspect added on 2015-07-16 00:04:47:
Just playing around with this on the script level, I've been able to simplify the test to the attached memleak-bovine.tcl + meminfo.tcl.

Output shows allocated bytes increasing by ~900 each 5 iterations, and (total mallocs-total frees) increasing by 25 - this all after the array is "full" and elements are being deleted.

Note this test doesn't require [lappend].  The dependency on [unset myarray($key)] is genuine -- using [array unset myarray $key] fails to exhibit the leak.

Other variations:

1. moving the entire body of [doit] into the loop at global scope doesn't leak
2. moving the body of [doit] into the loop and wrapping it in a proc leaks
3. making myarray proc-local instead of global leaks

aku added on 2015-07-15 23:10:10:
gist extracted and attached.

Attachments: