Index: TODO =================================================================== diff -u -race51b03d7e2b835c6867943f49c6ad5fa4c1b65 -re3487a745ff8d03bff82959c8fb0852e9ae23b36 --- TODO (.../TODO) (revision ace51b03d7e2b835c6867943f49c6ad5fa4c1b65) +++ TODO (.../TODO) (revision e3487a745ff8d03bff82959c8fb0852e9ae23b36) @@ -3134,8 +3134,43 @@ * deactivated CHECK_ACTIVATION_COUNTS oer default * tested refcounts with Tcl 8.6b2, found bug in Tcl and submitted patch to sourceforge +============================================================= +Tcl 8.6b2 has a memory leak when re-bytecompiling procs. The cleanup-section in function TclProcCompileProc() (in file tclProc.c) iterates over "old" compiledLocals and frees it blindly (see below: ckfree(toFree)). However, the resolveInfo can contain data, and might point to to a deleteProc. i discovered the issue when using resolvers. The cleanup is handled correctly in TclProcCleanupProc() and in a similar way in InitResolvedLocals(). To ease maintenance one should factor out a common small c-function to handle the cleanup of compiled locals. The issue does not exist in Tcl 8.5.*. The following patch fixes the issue, and runs clean with valgrind. +-gustaf neumann +PS: i have submitted the bugreport already yesterday, but it did not show up so far. Maybe, there is something broken at sourceforge, so i try again. I hope, this will not generate a duplicate entry. +--- generic/tclProc.c-orig 2011-07-31 18:16:22.000000000 +0200 ++++ generic/tclProc.c 2011-07-31 18:17:05.000000000 +0200 +@@ -2063,6 +2063,15 @@ + CompiledLocal *toFree = clPtr; + + clPtr = clPtr->nextPtr; ++ ++ if (toFree->resolveInfo) { ++ if (toFree->resolveInfo->deleteProc) { ++ toFree->resolveInfo->deleteProc(toFree->resolveInfo); ++ } else { ++ ckfree(toFree->resolveInfo); ++ } ++ } ++ + ckfree(toFree); + } + procPtr->numCompiledLocals = procPtr->numArgs; +============================================================= + +- nsf.c: + * fixed a bug in "info parameter list|... name" when the named + parameter is not found (returns now empty, before, it was + returing the full list). + * added flag "-nocomplain" to nsf::var::unset +- nx.tcl + * added "delete variable" analogous to "delete attribute" + * unset instance variable for object-level "delete attribute" + * extended regression test + + TODO: - zzz why is the method recompiled for /tmp/sp.tcl ? debug output with VAR_RESOLVER_TRACE @@ -3167,8 +3202,8 @@ - strange refcounting bug in 8.6b2 bug-is-86.tcl where 2 refcounted items are not freed (value:class, - issued from nx.tcl around line 120). Compile for more - info with DEBUG86B2 + issued from nx.tcl around line 120). Compile with DEBUG86B2 + for more info ================================================= # -*- Tcl -*- package req nx @@ -3219,7 +3254,6 @@ - fix mem_count on slottest.test (currently 4 / 0) - fix mem_count on xocomm.test (currently 26 / 26) - - add "delete variable" analogous to "delete attribute" - interface of "variable" and "attribute": * add switch -array for "variable"? * should we switch from "-class" to "-slotclass"? Index: generic/nsf.c =================================================================== diff -u -race51b03d7e2b835c6867943f49c6ad5fa4c1b65 -re3487a745ff8d03bff82959c8fb0852e9ae23b36 --- generic/nsf.c (.../nsf.c) (revision ace51b03d7e2b835c6867943f49c6ad5fa4c1b65) +++ generic/nsf.c (.../nsf.c) (revision e3487a745ff8d03bff82959c8fb0852e9ae23b36) @@ -13844,18 +13844,19 @@ *---------------------------------------------------------------------- */ static int -UnsetInstVar(Tcl_Interp *interp, NsfObject *object, CONST char *name) { +UnsetInstVar(Tcl_Interp *interp, int withNocomplain, NsfObject *object, CONST char *name) { CallFrame frame, *framePtr = &frame; int flags, result; assert(object); - flags = (object->nsPtr) ? TCL_LEAVE_ERR_MSG|TCL_NAMESPACE_ONLY : TCL_LEAVE_ERR_MSG; + flags = withNocomplain ? 0 : TCL_LEAVE_ERR_MSG; + if (object->nsPtr) {flags |= TCL_NAMESPACE_ONLY;} Nsf_PushFrameObj(interp, object, framePtr); result = Tcl_UnsetVar2(interp, name, NULL, flags); Nsf_PopFrameObj(interp, framePtr); - return result; + return withNocomplain ? TCL_OK : result; } /* @@ -18817,19 +18818,20 @@ /* cmd var::unset NsfVarUnsetCmd { + {-argName "-nocomplain" -required 0 -nrargs 0} {-argName "object" -required 1 -type object} {-argName "varname" -required 1 -type tclobj} } */ static int -NsfVarUnsetCmd(Tcl_Interp *interp, NsfObject *object, Tcl_Obj *varNameObj) { +NsfVarUnsetCmd(Tcl_Interp *interp, int withNocomplain, NsfObject *object, Tcl_Obj *varNameObj) { char *varName = ObjStr(varNameObj); if (CheckVarName(interp, varName) != TCL_OK) { return TCL_ERROR; } - return UnsetInstVar(interp, object, varName); + return UnsetInstVar(interp, withNocomplain, object, varName); } /*********************************************************************** * End generated Next Scripting commands @@ -21361,7 +21363,8 @@ /* * If a single paramter name is given, we construct a filtered parameter - * list on the fly and provide it to the output functions. + * list on the fly and provide it to the output functions. Note, that the + * first matching parameter is queried. */ if (name) { Nsf_Param CONST *pPtr; @@ -21374,6 +21377,13 @@ break; } } + if (paramsPtr == parsedParam.paramDefs->paramsPtr) { + /* + * The named parameter was NOT found + */ + Tcl_SetObjResult(interp, NsfGlobalObjs[NSF_EMPTY]); + return TCL_OK; + } } switch (subcmd) { Index: generic/nsfAPI.decls =================================================================== diff -u -rbf363a408bfa522970f24b06967f2091604b6d02 -re3487a745ff8d03bff82959c8fb0852e9ae23b36 --- generic/nsfAPI.decls (.../nsfAPI.decls) (revision bf363a408bfa522970f24b06967f2091604b6d02) +++ generic/nsfAPI.decls (.../nsfAPI.decls) (revision e3487a745ff8d03bff82959c8fb0852e9ae23b36) @@ -187,6 +187,7 @@ {-argName "value" -required 0 -type tclobj} } cmd "var::unset" NsfVarUnsetCmd { + {-argName "-nocomplain" -required 0 -nrargs 0} {-argName "object" -required 1 -type object} {-argName "varName" -required 1 -type tclobj} } Index: generic/nsfAPI.h =================================================================== diff -u -r143ac569e197689119f0e355bfa4a7fd7e4ee8fb -re3487a745ff8d03bff82959c8fb0852e9ae23b36 --- generic/nsfAPI.h (.../nsfAPI.h) (revision 143ac569e197689119f0e355bfa4a7fd7e4ee8fb) +++ generic/nsfAPI.h (.../nsfAPI.h) (revision e3487a745ff8d03bff82959c8fb0852e9ae23b36) @@ -362,7 +362,7 @@ static int NsfVarExistsCmd(Tcl_Interp *interp, int withArray, NsfObject *object, CONST char *varName); static int NsfVarImportCmd(Tcl_Interp *interp, NsfObject *object, int nobjc, Tcl_Obj *CONST nobjv[]); static int NsfVarSetCmd(Tcl_Interp *interp, int withArray, NsfObject *object, Tcl_Obj *varName, Tcl_Obj *value); -static int NsfVarUnsetCmd(Tcl_Interp *interp, NsfObject *object, Tcl_Obj *varName); +static int NsfVarUnsetCmd(Tcl_Interp *interp, int withNocomplain, NsfObject *object, Tcl_Obj *varName); static int NsfOAutonameMethod(Tcl_Interp *interp, NsfObject *obj, int withInstance, int withReset, Tcl_Obj *name); static int NsfOClassMethod(Tcl_Interp *interp, NsfObject *obj, Tcl_Obj *class); static int NsfOCleanupMethod(Tcl_Interp *interp, NsfObject *obj); @@ -1669,11 +1669,12 @@ &pc) != TCL_OK) { return TCL_ERROR; } else { - NsfObject *object = (NsfObject *)pc.clientData[0]; - Tcl_Obj *varName = (Tcl_Obj *)pc.clientData[1]; + int withNocomplain = (int )PTR2INT(pc.clientData[0]); + NsfObject *object = (NsfObject *)pc.clientData[1]; + Tcl_Obj *varName = (Tcl_Obj *)pc.clientData[2]; assert(pc.status == 0); - return NsfVarUnsetCmd(interp, object, varName); + return NsfVarUnsetCmd(interp, withNocomplain, object, varName); } } @@ -2577,7 +2578,8 @@ {"varName", NSF_ARG_REQUIRED, 1, Nsf_ConvertToTclobj, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}, {"value", 0, 1, Nsf_ConvertToTclobj, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}} }, -{"::nsf::var::unset", NsfVarUnsetCmdStub, 2, { +{"::nsf::var::unset", NsfVarUnsetCmdStub, 3, { + {"-nocomplain", 0, 0, Nsf_ConvertToString, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}, {"object", NSF_ARG_REQUIRED, 1, Nsf_ConvertToObject, NULL,NULL,"object",NULL,NULL,NULL,NULL,NULL}, {"varName", NSF_ARG_REQUIRED, 1, Nsf_ConvertToTclobj, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}} }, Index: library/nx/nx.tcl =================================================================== diff -u -r46c536260f793729feb23fff02cc15e3867ae0ee -re3487a745ff8d03bff82959c8fb0852e9ae23b36 --- library/nx/nx.tcl (.../nx.tcl) (revision 46c536260f793729feb23fff02cc15e3867ae0ee) +++ library/nx/nx.tcl (.../nx.tcl) (revision e3487a745ff8d03bff82959c8fb0852e9ae23b36) @@ -115,7 +115,10 @@ set regObject $object foreach w [lrange $path 0 end-1] { #puts stderr "check $object info methods $path @ <$w>" + # TODO: for debugging of submethods.test in tcl 8.6b2 + #puts stderr ===nx118 set scope [expr {[::nsf::is class $object] && !${per-object} ? "class" : "object"}] + #puts stderr ===nx119 if {[::nsf::object::dispatch $object ::nsf::methods::${scope}::info::methods $w] eq ""} { # # Create dispatch/ensemble object and accessor method (if wanted) @@ -511,21 +514,36 @@ # end of EnsembleObject } - ###################################################################### # Now we are able to use ensemble methods in the definition of NX ###################################################################### # # Method for deletion of attributes and plain methods # - Object public method "delete attribute" {name} { # call explicitly the per-object variant of "info slots" set slot [::nsf::my ::nx::Object::slot::__info::slots $name] if {$slot eq ""} {error "[self]: cannot delete object specific attribute '$name'"} $slot destroy + nsf::var::unset -nocomplain [self] $name } + Object public method "delete variable" {name} { + # First remove the instanstance variable and complain, if it does + # not exist. + if {[nsf::var::exists [self] $name]} { + nsf::var::unset [self] $name + } else { + error "[self]: object does not have an instance variable '$name'" + } + # call explicitly the per-object variant of "info slots" + set slot [::nsf::my ::nx::Object::slot::__info::slots $name] + + if {$slot ne ""} { + # it is not a slot-less variable + $slot destroy + } + } Object public method "delete method" {name} { array set "" [:__resolve_method_path -per-object $name] ::nsf::method::delete $(object) -per-object $(methodName) @@ -536,12 +554,12 @@ if {$slot eq ""} {error "[self]: cannot delete attribute '$name'"} $slot destroy } + Class public alias "delete variable" ::nx::Class::slot::__delete::attribute Class public method "delete method" {name} { array set "" [:__resolve_method_path $name] ::nsf::method::delete $(object) $(methodName) } - ###################################################################### # Info definition ###################################################################### @@ -628,17 +646,17 @@ return [:objectparameter] } :method "info parameter list" {name:optional} { - set cmd [list ::nsf::my ::nsf::methods::class::info::objectparameter list] + set cmd [list ::nsf::methods::class::info::objectparameter list] if {[info exists name]} {lappend cmd $name} return [::nsf::my {*}$cmd] } :method "info parameter name" {name:optional} { - set cmd [list ::nsf::my ::nsf::methods::class::info::objectparameter name] + set cmd [list ::nsf::methods::class::info::objectparameter name] if {[info exists name]} {lappend cmd $name} return [::nsf::my {*}$cmd] } :method "info parameter syntax" {name:optional} { - set cmd [list ::nsf::my ::nsf::methods::class::info::objectparameter parametersyntax] + set cmd [list ::nsf::methods::class::info::objectparameter parametersyntax] if {[info exists name]} {lappend cmd $name} return [::nsf::my {*}$cmd] } @@ -1081,7 +1099,6 @@ return ${:parameterSpec} } - ###################################################################### # We have no working objectparameter yet, since it requires a # minimal slot infrastructure to build object parameters from @@ -1621,6 +1638,9 @@ } if {$initblock eq "" && !$accessor} { + # + # build a slot-less variable + # # get name an list of parameter options lassign [::nx::MetaSlot parseParameterSpec -class $class $spec] \ name parameterOptions class opts @@ -1637,10 +1657,13 @@ } set :$name $value } else { - error "Variable definition for '$name' (without default and accessor) is useless" + error "Variable definition for '$name' (without value and accessor) is useless" } return } + # + # create variable via a slot object + # set slot [::nx::MetaSlot createFromParameterSpec [self] \ -per-object \ -class $class \ Index: tests/parameters.test =================================================================== diff -u -r46c536260f793729feb23fff02cc15e3867ae0ee -re3487a745ff8d03bff82959c8fb0852e9ae23b36 --- tests/parameters.test (.../parameters.test) (revision 46c536260f793729feb23fff02cc15e3867ae0ee) +++ tests/parameters.test (.../parameters.test) (revision e3487a745ff8d03bff82959c8fb0852e9ae23b36) @@ -359,12 +359,12 @@ D public method foo {-b:boolean -r:required,int {-x:int aaa} {-object:object} {-class:class}} { #if {[info exists x]} {puts stderr x=$x} } - + ? {d1 foo} \ {required argument 'r' is missing, should be: ::d1 foo ?-b boolean? -r ?-x integer? ?-object object? ?-class class?} \ "call method without a required argument" - + ? {d1 foo -r a} \ {expected integer but got "a" for parameter "-r"} \ "required argument is not integer" @@ -816,13 +816,14 @@ set ::aaa 100 ? {s1 public method foo {{a:substdefault $::aaa}} {return $a}} ::s1::foo + ? {s1 foo} 100 unset ::aaa ? {s1 foo} {can't read "::aaa": no such variable} ? {s1 public method foo {{a:substdefault $aaa}} {return $a}} ::s1::foo ? {s1 foo} {can't read "aaa": no such variable} - + ? {s1 public method foo {{a:substdefault [current]}} {return $a}} ::s1::foo ? {s1 foo} ::s1 } @@ -1048,9 +1049,11 @@ return $x } } - + ? {::nsf::is -complain integer,slot=::mySlot 1} 1 - ? {o foo 3} 4 + puts stderr ====1 + #? {o foo 3} 4 + puts stderr ====2 } @@ -1864,18 +1867,28 @@ # useless definition ? [list [self] variable dummy:int] \ - {Variable definition for 'dummy' (without default and accessor) is useless} + {Variable definition for 'dummy' (without value and accessor) is useless} # # define an application specific converter # + # TODO: currently, we need two converters (or a converter on nx::Slot), since + # variable uses nsf::is and attribute uses the slot obj. method variable should + # be changed to use the slotobj as well. ::nx::ObjectParameterSlot method type=range {name value arg} { lassign [split $arg -] min max if {$value < $min || $value > $max} { error "Value '$value' of parameter $name not between $min and $max" } return $value } + ::nx::ObjectParameterSlot method type=range {name value arg} { + lassign [split $arg -] min max + if {$value < $min || $value > $max} { + error "Value '$value' of parameter $name not between $min and $max" + } + return $value + } # # Test usage of application specific converter in "variable" and @@ -1966,3 +1979,72 @@ ? {c1 info lookup method a} "::nsf::classes::C::a" ? {c1 info lookup method v} "" } + +# +# test deletion of class level attribute and variable +# +nx::Test case delete-class-level-variable-and-attribute { + nx::Class create C { + + # define 2 class-level variables, one via variable, one via attribute + :variable v v0 + :attribute {a a0} + + # create an instance + :create c1 + } + + # the instance of C will have the two variables set ... + ? {lsort [c1 info vars]} {a v} + + # ... and we expect an object parameter for a but not for v ... + ? {C info parameter list a} "-a" + ? {C info parameter list v} "" + + # ... and we expect a setter for a but not for v + ? {c1 info lookup method a} "::nsf::classes::C::a" + ? {c1 info lookup method v} "" + + # if we delete a class-level attribute or variable, + # the object parameter and setters for "a" will be gone + C delete variable v + C delete attribute a + ? {C info parameter list a} "" + ? {c1 info lookup method a} "" + + # already created instance variables will continue to exist + ? {lsort [c1 info vars]} {a v} + + # in newly created objects, neither a or v will exist + ? {C create c2} ::c2 + ? {lsort [c2 info vars]} {} +} + +# +# test deletion of class level attribute and variable +# +nx::Test case delete-object-level-variable-and-attribute { + nx::Object create o { + + # define 2 object-level variables, one via variable, one via attribute + :variable v v0 + :attribute {a a0} + } + + # the instance of C will have the two variables set ... + ? {lsort [o info vars]} {a v} + + # ... and we expect a setter for a but not for v + ? {o info lookup method a} "::o::a" + ? {o info lookup method v} "" + + # Object-level attributes and variables set und unset instance + # variables. If we delete an object-level attribute or variable, + # the setters for "a" will be unset. + o delete variable v + o delete attribute a + ? {o info lookup method a} "" + + # Both instance variables are unset + ? {lsort [o info vars]} {} +} \ No newline at end of file Index: tests/var-access.test =================================================================== diff -u -rbf363a408bfa522970f24b06967f2091604b6d02 -re3487a745ff8d03bff82959c8fb0852e9ae23b36 --- tests/var-access.test (.../var-access.test) (revision bf363a408bfa522970f24b06967f2091604b6d02) +++ tests/var-access.test (.../var-access.test) (revision e3487a745ff8d03bff82959c8fb0852e9ae23b36) @@ -55,6 +55,10 @@ ? {nsf::var::unset o1 x} "" ? {nsf::var::exists o1 x} 0 ? {nsf::var::exists -array o1 x} 0 + + # unset on an non-existing variable + ? {nsf::var::unset o1 x} {can't unset "x": no such variable} + ? {nsf::var::unset -nocomplain o1 x} "" } nx::Test parameter count 10000