diff options
author | dgp <dgp@users.sourceforge.net> | 2016-09-07 17:45:33 (GMT) |
---|---|---|
committer | dgp <dgp@users.sourceforge.net> | 2016-09-07 17:45:33 (GMT) |
commit | b2285feb18ebbcf95efeec554d49294c0d6c499a (patch) | |
tree | 7707f92ef5581b074d89e0165ccf17f1d43cb680 | |
parent | ea21bb5de3ca09b64d101656f0613cd10f877aa6 (diff) | |
parent | f4881ad277e61ae34673ee464a4d43ac5da32ab3 (diff) | |
download | tcl-b2285feb18ebbcf95efeec554d49294c0d6c499a.zip tcl-b2285feb18ebbcf95efeec554d49294c0d6c499a.tar.gz tcl-b2285feb18ebbcf95efeec554d49294c0d6c499a.tar.bz2 |
[4dbdd9af14] Plug mem leak when var unset trace re-creates namespace var. Thanks mr_calvin for report and fix.
-rw-r--r-- | generic/tclVar.c | 24 | ||||
-rw-r--r-- | tests/var.test | 30 |
2 files changed, 49 insertions, 5 deletions
diff --git a/generic/tclVar.c b/generic/tclVar.c index 2adffbc..46a1da6 100644 --- a/generic/tclVar.c +++ b/generic/tclVar.c @@ -5193,13 +5193,16 @@ TclDeleteNamespaceVars( VarHashRefCount(varPtr)++; /* Make sure we get to remove from * hash. */ Tcl_GetVariableFullName(interp, (Tcl_Var) varPtr, objPtr); - UnsetVarStruct(varPtr, NULL, iPtr, /* part1 */ objPtr, NULL, flags, - -1); - Tcl_DecrRefCount(objPtr); /* Free no longer needed obj */ + UnsetVarStruct(varPtr, NULL, iPtr, /* part1 */ objPtr, + NULL, flags, -1); /* - * Remove the variable from the table and force it undefined in case - * an unset trace brought it back from the dead. + * We just unset the variable. However, an unset trace might + * have re-set it, or might have re-established traces on it. + * This namespace and its vartable are going away unconditionally, + * so we cannot let such things linger. That would be a leak. + * + * First we destroy all traces. ... */ if (TclIsVarTraced(varPtr)) { @@ -5223,6 +5226,17 @@ TclDeleteNamespaceVars( } } } + + /* + * ...and then, if the variable still holds a value, we unset it + * again. This time with no traces left, we're sure it goes away. + */ + + if (!TclIsVarUndefined(varPtr)) { + UnsetVarStruct(varPtr, NULL, iPtr, /* part1 */ objPtr, + NULL, flags, -1); + } + Tcl_DecrRefCount(objPtr); /* free no longer needed obj */ VarHashRefCount(varPtr)--; VarHashDeleteEntry(varPtr); } diff --git a/tests/var.test b/tests/var.test index 297034a..a9d93ac 100644 --- a/tests/var.test +++ b/tests/var.test @@ -26,6 +26,20 @@ testConstraint testupvar [llength [info commands testupvar]] testConstraint testgetvarfullname [llength [info commands testgetvarfullname]] testConstraint testsetnoerr [llength [info commands testsetnoerr]] testConstraint memory [llength [info commands memory]] +if {[testConstraint memory]} { + proc getbytes {} { + return [lindex [split [memory info] \n] 3 3] + } + proc leaktest {script {iterations 3}} { + set end [getbytes] + for {set i 0} {$i < $iterations} {incr i} { + uplevel 1 $script + set tmp $end + set end [getbytes] + } + return [expr {$end - $tmp}] + } +} catch {rename p ""} catch {namespace delete test_ns_var} @@ -579,6 +593,22 @@ test var-8.2 {TclDeleteNamespaceVars, "unset" traces on ns delete are called wit list [namespace delete test_ns_var] $::info } -result {{} {::test_ns_var::v {} u}} +test var-8.3 {TclDeleteNamespaceVars, mem leak} -constraints memory -setup { + proc ::t {a i o} { + set $a 321 + } +} -body { + leaktest { + namespace eval n { + variable v 123 + trace variable v u ::t + } + namespace delete n + } +} -cleanup { + rename ::t {} +} -result 0 + test var-9.1 {behaviour of TclGet/SetVar simple get/set} -setup { catch {unset u} catch {unset v} |