Index: generic/nsf.c =================================================================== diff -u -r3e18b80be2883ba647c2110a2e8e2b1980940c30 -rf34be2f016a50e3f5fc8b1b021e28fb696bdb5de --- generic/nsf.c (.../nsf.c) (revision 3e18b80be2883ba647c2110a2e8e2b1980940c30) +++ generic/nsf.c (.../nsf.c) (revision f34be2f016a50e3f5fc8b1b021e28fb696bdb5de) @@ -4913,10 +4913,18 @@ * remove all command pointers from a list that have a bumped epoch */ static void -CmdListRemoveEpoched(INTERP_DECL NsfCmdList **cmdList, NsfFreeCmdListClientData *freeFct) { +CmdListRemoveDeleted(INTERP_DECL NsfCmdList **cmdList, NsfFreeCmdListClientData *freeFct) { NsfCmdList *f = *cmdList, *del; while (f) { - if (Tcl_Command_cmdEpoch(f->cmdPtr)) { + /* + * HIDDEN OBJECTS: For supporting hidden mixins, we cannot rely on the + * cmdEpoch as indicator of the deletion status of a cmd because the epoch + * counters of hidden and re-exposed commands are bumped. Despite of this, + * their object structures remain valid. We resort to the use of the + * per-cmd flag CMD_IS_DELETED, set upon processing a command in + * Tcl_DeleteCommandFromToken(). + */ + if (Tcl_Command_flags(f->cmdPtr) & CMD_IS_DELETED /* Tcl_Command_cmdEpoch(f->cmdPtr) */) { del = f; f = f->nextPtr; del = CmdListRemoveFromList(cmdList, del); @@ -4935,7 +4943,7 @@ NsfFreeCmdListClientData *freeFct) { NsfCmdList *c, *del = NULL; /* - CmdListRemoveEpoched(INTERP cmdList, freeFct); + CmdListRemoveDeleted(INTERP cmdList, freeFct); */ c = *cmdList; while (c && c->clorobj == clorobj) { @@ -5446,7 +5454,7 @@ NsfCmdList *m; NsfClasses *pl, **clPtr = mixinClasses; - CmdListRemoveEpoched(INTERP mixinList, GuardDel); + CmdListRemoveDeleted(INTERP mixinList, GuardDel); for (m = *mixinList; m; m = m->nextPtr) { NsfClass *mCl = NsfGetClassFromCmdPtr(m->cmdPtr); @@ -5511,7 +5519,7 @@ *---------------------------------------------------------------------- * NsfClassListAddPerClassMixins -- * - * Append the class mixins to the proivded list. CheckList is used to + * Append the class mixins to the provided list. CheckList is used to * eliminate potential duplicates. * * Results: @@ -6652,11 +6660,10 @@ *---------------------------------------------------------------------- * MixinSearchProc -- * - * Search for a methodname in the mixin list of the provided - * object. According to the state of the mixin stack it start the search - * from the beginning of from the last dispatched method shadowed method on - * the mixin path. If a class *cl and a *cmdPtr are provided, the function - * only succeeds, when the class is a mixin class. + * Search for a method name in the mixin list of the provided + * object. Depending on the state of the mixin stack, the search starts + * at the beginning or at the last dispatched, shadowed method on + * the mixin path. * * Results: * Tcl result code. @@ -6698,8 +6705,16 @@ for (; cmdList; cmdList = cmdList->nextPtr) { NsfClass *cl1; - - if (Tcl_Command_cmdEpoch(cmdList->cmdPtr)) { continue; } + + /* + * HIDDEN OBJECTS: For supporting hidden mixins, we cannot rely on the + * cmdEpoch as indicator of the deletion status of a cmd because the + * epoch counters of hidden and re-exposed commands are bumped. Despite + * of this, their object structures remain valid. + */ + if (Tcl_Command_flags(cmdList->cmdPtr) & CMD_IS_DELETED + /*Tcl_Command_cmdEpoch(cmdList->cmdPtr)*/) { continue; } + cl1 = NsfGetClassFromCmdPtr(cmdList->cmdPtr); assert(cl1); lastCmdPtr = cmdList->cmdPtr; @@ -6748,8 +6763,13 @@ } else { for (; cmdList; cmdList = cmdList->nextPtr) { - - if (Tcl_Command_cmdEpoch(cmdList->cmdPtr)) { + /* + * HIDDEN OBJECTS: For supporting hidden mixins, we cannot rely on the + * cmdEpoch as indicator of the deletion status of a cmd because the + * epoch counters of hidden and re-exposed commands are bumped. Despite + * of this, their object structures remain valid. + */ + if (Tcl_Command_flags(cmdList->cmdPtr) & CMD_IS_DELETED /*Tcl_Command_cmdEpoch(cmdList->cmdPtr)*/) { continue; } cl = NsfGetClassFromCmdPtr(cmdList->cmdPtr); @@ -7213,7 +7233,7 @@ NsfCmdList *cmdList, *del; NsfClass *cl = NULL; - CmdListRemoveEpoched(INTERP filters, GuardDel); + CmdListRemoveDeleted(INTERP filters, GuardDel); for (cmdList = *filters; cmdList; ) { simpleName = (char *) Tcl_GetCommandName(interp, cmdList->cmdPtr); cmd = FilterSearch(INTERP simpleName, startingObject, startingClass, &cl); @@ -7404,7 +7424,7 @@ /* * ensure that no epoched command is in the filters list */ - CmdListRemoveEpoched(INTERP filters, GuardDel); + CmdListRemoveDeleted(INTERP filters, GuardDel); for (f = *filters; f; f = f->nextPtr) { simpleName = (char *) Tcl_GetCommandName(interp, f->cmdPtr); Index: tests/interp.test =================================================================== diff -u -r3fd99736ac596563e18a0f8c242f2da4fc0cb2bf -rf34be2f016a50e3f5fc8b1b021e28fb696bdb5de --- tests/interp.test (.../interp.test) (revision 3fd99736ac596563e18a0f8c242f2da4fc0cb2bf) +++ tests/interp.test (.../interp.test) (revision f34be2f016a50e3f5fc8b1b021e28fb696bdb5de) @@ -508,4 +508,253 @@ ? {interp eval $i {nsf::object::exists ::o}} 0 interp delete $i -} \ No newline at end of file +} + +# +# see NsfProcAliasMethod(): +# Tcl_Command_cmdEpoch(tcd->aliasedCmd) +# +nx::Test case hidden-procs-as-aliases { + # + # 1) hide alias proc targets + # + global i + set i [interp create] + $i eval { + package req nx + ::proc ::FOO args {return OK} + nx::Object create o { + :public alias foo ::FOO + } + } + + ? {$i eval {o foo}} OK + ? {$i hidden} "" + $i hide FOO + ? {$i hidden} FOO + # + # For now, we do not allow to dispatch to hidden proc targets + # through their method aliases as this would counteract the idea of + # hiding cmds from a (safe) slave interp. As the NSF aliasing works + # unrestrictedly in child (safe) interps (as opposed to [interp + # invokehidden]), this would derail the essentials of the hiding + # mechanism. + # + ? {$i eval {o foo}} {target "::FOO" of alias foo apparently disappeared} + # + # When exposing it again (e.g., from the master interp), we can + # dispatch again; note this is currently limited to the exposing + # under the original command name (!) + # + $i expose FOO + ? {$i hidden} "" + ? {$i eval {o foo}} OK + + $i hide FOO + ? {$i hidden} FOO + + # + # Limitation: Currently, exposing a hidden target command under a + # *different* name will not re-establish the alias. This is due to + # the way NsfProcAliasMethod() is currently implemented: Rebinding + # an epoched cmd (which holds for renamed as well as + # hidden/re-exposed cmds) is currently based on the command name + # stored in the ::nsf::alias array. This metadata store is not + # maintained during [interp hide|expose] operations. Using a + # pointer-based (reverse) lookup based on tcd->aliasedCmd would be + # possible (I did it testwise), but then we would have to revise the + # current behaviour of NsfProcAliasMethod() for target proc + # renamings also. A non-deleting [rename] currently also interrupts + # an alias binding. See the relevant tests on [rename ::foo ::foo2] + # in tests/alias.test. To be consistent, and because [interp + # hide|expose] is a two-step [rename], technically, we keep the + # current behaviour. + # + $i expose FOO OOF + + ? {$i hidden} "" + ? {$i eval {o foo}} {target "::FOO" of alias foo apparently disappeared} + + # + # Due to the alias-specific lookup scheme (::nsf::alias), we could fix + # the alias manually after a command-renaming hide|expose operation: + # + + ? {$i eval {info exists ::nsf::alias(::o,foo,1)}} 1 + ? {$i eval {set ::nsf::alias(::o,foo,1)}} "::FOO" + ? {$i eval {set ::nsf::alias(::o,foo,1) ::OOF}} "::OOF" + ? {$i eval {info commands ::OOF}} ::OOF + ? {$i eval {o foo}} OK + + interp delete $i + unset i + +} + +nx::Test case hidden-objects-as-aliases { + # + # 2) hide alias object targets + # + global i + set i [interp create] + $i eval { + package req nx + nx::Object create x { + :public method foo {} {return OK} + } + nx::Object create dongo { + :public alias bar ::x + } + } + + # + # Objects as intermediary aliases are transparent when being + # hidden|exposed; hiding and exposing them (under differing command + # names) do not affect the dispatch behaviour; this is due to the + # ensemble dispatch strategy ... + # + + ? {$i hidden} "" + ? {$i eval {dongo bar foo}} OK + $i hide x + ? {$i hidden} x + ? {$i eval {dongo bar foo}} OK + ? {$i eval {x foo}} {invalid command name "x"} + ? {$i invokehidden x foo} OK + $i expose x + ? {$i hidden} "" + ? {$i eval {dongo bar foo}} OK + ? {$i eval {x foo}} OK + + $i hide x X + ? {$i hidden} X + ? {$i eval {dongo bar foo}} OK + ? {$i eval {X foo}} {invalid command name "X"} + ? {$i invokehidden X foo} OK + + $i expose X XX + ? {$i hidden} "" + ? {$i eval {dongo bar foo}} OK + ? {$i eval {XX foo}} OK + + # + # Hiding of leaf methods (e.g., ::o::foo) is not an issue because + # hiding|exposing is limited to global commands + # + + ? {$i hide ::o::foo} "cannot use namespace qualifiers in hidden command token (rename)" + + interp delete $i + unset i +} + +# +# MixinSearchProc() +# + +nx::Test case hidden-mixins-procsearch { + global i + set i [interp create] + $i eval { + package req nx + nx::Object create x { + :public method foo {} {return OK} + } + nx::Class create M { + :public method foo {} { + return <[current class]>[next]<[current class]> + } + } + x mixin M + } + + ? {$i eval {x foo}} <::M>OK<::M> + + # + # Hiding M as a command should not affect *existing* mixin + # relations! + # + + $i hide M + ? {$i hidden} M + ? {$i eval {x foo}} <::M>OK<::M> "with hidden mixin" + $i expose M + ? {$i hidden} "" + ? {$i eval {x foo}} <::M>OK<::M> "with re-exposed mixin" + + $i hide M m + ? {$i eval {x foo}} <::M>OK<::M> "with hidden mixin (renamed command)" + $i expose m MM + ? {$i eval {x foo}} <::M>OK<::M> "with re-exposed mixin (renamed command)" + + # + # However, modifying mixin axes is hindered, because of + # the underlying relation machinery (::nsf::relation, + # RelationSlot->add(), etc.) is relying on exposed command names + # (i.e., command and object look-ups based on + # Tcl_GetCommandFromToken(), GetObjectFromString() usage). + # + + $i hide MM M + $i eval {nx::Class create ::M2} + ? {$i eval {x mixin add M2}} {mixin: expected a class as mixin but got "::M"} + ? {$i invokehidden M mixin add M2} {expected object but got "::M" for parameter "object"} + + interp delete $i + unset i +} + +# +# MixinComputeOrderFullList() & friends (due to +# CmdListRemoveDeleted() +# +nx::Test case hidden-mixins-mixinlists { + global i + set i [interp create] + $i eval { + package req nx + nx::Object create o + nx::Class create M1 + nx::Class create M2 + nx::Class create M3 + o mixin {M1 M2} + } + + ? {$i eval {o info precedence}} "::M1 ::M2 ::nx::Object" + ? {$i eval {o info mixin classes}} {::M1 ::M2} + ? {$i hidden} "" + $i hide M1 + ? {$i hidden} M1 + $i eval {M2 mixin add M3} + ? {$i eval {o info precedence}} "::M1 ::M3 ::M2 ::nx::Object" + # + # Now, have the mixin list invalidated; The next time we request the list, + # the mixin assembly machinery will have to deal with the hidden + # mixin; and properly include it. + # + # We need to destroy one mixin explicitly (or add one as a per-class + # mixin, or the like), as the mixin modification API would stumble + # over the hidden mixin object ... + # + $i eval {::M2 destroy} + ? {$i eval {o info precedence}} "::M1 ::nx::Object" + ? {$i eval {o info mixin classes}} "::M1" + ? {$i invokehidden M1 info mixinof} "::o" + + interp delete $i + unset i + + # $i eval { + # nx::Class create ::M2 + # # catch {x mixin add ::M2} msg + # # puts stderr -----msg=$msg + # # TODO: force an invalidation of the mixin order! + # # + # catch { interp invokehidden {} M mixin add M2 } msg + # puts stderr -----msg=$msg,$::errorInfo + # } + # ? {$i eval {x info precedence}} "::M ::nx::Object" + # ? {$i eval {x info mixin classes}} ::M +} + +