From 0175377f5e1c38829cb31dfa14ee9d28714e9a57 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 5 Mar 2024 16:38:57 +0000 Subject: fix for [910d67a229fe7f65]: search of `namespace unknown` handler fixed: first try to find namespace unknown handler of the namespace of executed command if available; this elementary fixes following bug (additionally prevents to call slowly global "::unknown" for known/loaded namespaces with registered unknown handler) --- generic/tclBasic.c | 29 +++++++++++++++++++++++++---- tests/namespace.test | 9 +++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/generic/tclBasic.c b/generic/tclBasic.c index 8dde621..93c3b43 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -4726,12 +4726,33 @@ TEOV_NotFound( * unknown command handler for the current * namespace (TIP 181). */ Namespace *savedNsPtr = NULL; + + int qualLen; + const char *qualName = TclGetStringFromObj(objv[0], &qualLen); currNsPtr = varFramePtr->nsPtr; - if ((currNsPtr == NULL) || (currNsPtr->unknownHandlerPtr == NULL)) { - currNsPtr = iPtr->globalNsPtr; - if (currNsPtr == NULL) { - Tcl_Panic("Tcl_EvalObjv: NULL global namespace pointer"); + if ((currNsPtr == NULL) || (currNsPtr->unknownHandlerPtr == NULL) || + (qualLen > 2 && (*qualName == ':') && (*(qualName+1) == ':')) + ) { + /* + * first try to find namespace unknown handler of the namespace + * of executed command if available: + */ + Namespace *dummyNsPtr; + const char *simpleName; + + (void) TclGetNamespaceForQualName(interp, qualName, currNsPtr, + TCL_NAMESPACE_ONLY, &currNsPtr, &dummyNsPtr, &dummyNsPtr, + &simpleName); + if ((currNsPtr == NULL) || (simpleName == NULL) || + currNsPtr->unknownHandlerPtr == NULL || + (currNsPtr->flags & (NS_DYING | NS_DEAD)) + ) { + /* fallback to the global unknown */ + currNsPtr = iPtr->globalNsPtr; + if (currNsPtr == NULL) { + Tcl_Panic("TEOV_NotFound: NULL global namespace pointer"); + } } } diff --git a/tests/namespace.test b/tests/namespace.test index 08531e4..74cd6a9 100644 --- a/tests/namespace.test +++ b/tests/namespace.test @@ -3047,6 +3047,15 @@ test namespace-52.12 {unknown: error case must not reset handler} -body { } -cleanup { namespace delete foo } -result ok +test namespace-52.13 {unknown: invocation outside of NS doesn't evade namespace unknown, bug 910d67a229fe7f65} -body { + namespace eval ::foo::bar { + proc _unknown args {list ::foo:bar:_unknown [uplevel {namespace current}] $args} + namespace unknown [namespace current]::_unknown + } + list [namespace inscope ::foo::bar {xxx}] [namespace inscope ::foo {bar::xxx}] [::foo::bar::xxx] +} -cleanup { + namespace delete ::foo +} -result {{::foo:bar:_unknown ::foo::bar xxx} {::foo:bar:_unknown ::foo bar::xxx} {::foo:bar:_unknown :: ::foo::bar::xxx}} # TIP 314 - ensembles with parameters test namespace-53.1 {ensembles: parameters} { -- cgit v0.12 From 5a74f345ac466a0a61b8c46bdcb3576848e6f987 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 6 Mar 2024 12:05:31 +0000 Subject: small amend to [910d67a229fe7f65]: additional corner case (see the test namespace-52.13) --- generic/tclBasic.c | 2 +- tests/namespace.test | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/generic/tclBasic.c b/generic/tclBasic.c index 93c3b43..ffd69c4 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -4732,7 +4732,7 @@ TEOV_NotFound( currNsPtr = varFramePtr->nsPtr; if ((currNsPtr == NULL) || (currNsPtr->unknownHandlerPtr == NULL) || - (qualLen > 2 && (*qualName == ':') && (*(qualName+1) == ':')) + (qualLen > 2 && memchr(qualName, ':', qualLen)) /* fast check for NS:: */ ) { /* * first try to find namespace unknown handler of the namespace diff --git a/tests/namespace.test b/tests/namespace.test index 74cd6a9..c8c1992 100644 --- a/tests/namespace.test +++ b/tests/namespace.test @@ -3052,10 +3052,10 @@ test namespace-52.13 {unknown: invocation outside of NS doesn't evade namespace proc _unknown args {list ::foo:bar:_unknown [uplevel {namespace current}] $args} namespace unknown [namespace current]::_unknown } - list [namespace inscope ::foo::bar {xxx}] [namespace inscope ::foo {bar::xxx}] [::foo::bar::xxx] + list [namespace inscope ::foo::bar {xxx}] [namespace inscope ::foo {bar::xxx}] [::foo::bar::xxx] [namespace inscope :: {foo::bar::xxx}] } -cleanup { namespace delete ::foo -} -result {{::foo:bar:_unknown ::foo::bar xxx} {::foo:bar:_unknown ::foo bar::xxx} {::foo:bar:_unknown :: ::foo::bar::xxx}} +} -result {{::foo:bar:_unknown ::foo::bar xxx} {::foo:bar:_unknown ::foo bar::xxx} {::foo:bar:_unknown :: ::foo::bar::xxx} {::foo:bar:_unknown :: foo::bar::xxx}} # TIP 314 - ensembles with parameters test namespace-53.1 {ensembles: parameters} { -- cgit v0.12 From ea30fa77faf70296eb26e071f149f8a935a8bc30 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 6 Mar 2024 19:22:10 +0000 Subject: better variant of fix for [910d67a229fe7f65] with improved search for NS::command (find NS even if command is not simple name), additionally it'd invoke handler of parent NS if child NS doesn't have unknown handler (see test namespace-52.14) --- generic/tclBasic.c | 14 ++++++++++++-- generic/tclInt.h | 3 +++ generic/tclNamesp.c | 20 ++++++++++++-------- tests/namespace.test | 16 ++++++++++++++++ 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/generic/tclBasic.c b/generic/tclBasic.c index ffd69c4..78685f0 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -4741,13 +4741,23 @@ TEOV_NotFound( Namespace *dummyNsPtr; const char *simpleName; + tryParentNS: (void) TclGetNamespaceForQualName(interp, qualName, currNsPtr, - TCL_NAMESPACE_ONLY, &currNsPtr, &dummyNsPtr, &dummyNsPtr, - &simpleName); + TCL_NAMESPACE_ONLY | TCL_FIND_IF_NOT_SIMPLE, &currNsPtr, + &dummyNsPtr, &dummyNsPtr, &simpleName); if ((currNsPtr == NULL) || (simpleName == NULL) || currNsPtr->unknownHandlerPtr == NULL || (currNsPtr->flags & (NS_DYING | NS_DEAD)) ) { + /* traverse to alive parent namespace containing handler */ + if (currNsPtr) { + qualName = currNsPtr->fullName; + qualLen = strlen(qualName); + if (qualLen > 2 && memchr(qualName, ':', qualLen)) { + currNsPtr = iPtr->globalNsPtr; + goto tryParentNS; + } + } /* fallback to the global unknown */ currNsPtr = iPtr->globalNsPtr; if (currNsPtr == NULL) { diff --git a/generic/tclInt.h b/generic/tclInt.h index 68c07f2..de92a7d 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -400,10 +400,13 @@ struct NamespacePathEntry { * TCL_NAMESPACE_ONLY - (see tcl.h) Look only in the context ns. * TCL_CREATE_NS_IF_UNKNOWN - Create unknown namespaces. * TCL_FIND_ONLY_NS - The name sought is a namespace name. + * TCL_FIND_IF_NOT_SIMPLE - Retrieve last namespace even if the rest of + * name is not simple name (contains ::). */ #define TCL_CREATE_NS_IF_UNKNOWN 0x800 #define TCL_FIND_ONLY_NS 0x1000 +#define TCL_FIND_IF_NOT_SIMPLE 0x2000 /* * The client data for an ensemble command. This consists of the table of diff --git a/generic/tclNamesp.c b/generic/tclNamesp.c index 290dcea..37092fe 100644 --- a/generic/tclNamesp.c +++ b/generic/tclNamesp.c @@ -2280,11 +2280,8 @@ TclGetNamespaceForQualName( if (flags & TCL_FIND_ONLY_NS) { nsName = start; } else { - *nsPtrPtr = nsPtr; - *altNsPtrPtr = altNsPtr; *simpleNamePtr = start; - Tcl_DStringFree(&buffer); - return TCL_OK; + goto done; } } else { /* @@ -2334,6 +2331,15 @@ TclGetNamespaceForQualName( } } else { /* Namespace not found and was not * created. */ + if (flags & TCL_FIND_IF_NOT_SIMPLE) { + /* + * return last found NS and not simple name relative it, + * e. g. ::A::B::C::D -> ::A::B and C::D, if + * namespace C cannot be found in ::A::B + */ + *simpleNamePtr = start; + goto done; + } nsPtr = NULL; } } @@ -2364,11 +2370,8 @@ TclGetNamespaceForQualName( */ if ((nsPtr == NULL) && (altNsPtr == NULL)) { - *nsPtrPtr = NULL; - *altNsPtrPtr = NULL; *simpleNamePtr = NULL; - Tcl_DStringFree(&buffer); - return TCL_OK; + goto done; } start = end; @@ -2398,6 +2401,7 @@ TclGetNamespaceForQualName( nsPtr = NULL; } +done: *nsPtrPtr = nsPtr; *altNsPtrPtr = altNsPtr; Tcl_DStringFree(&buffer); diff --git a/tests/namespace.test b/tests/namespace.test index c8c1992..5a8f6f4 100644 --- a/tests/namespace.test +++ b/tests/namespace.test @@ -3056,6 +3056,22 @@ test namespace-52.13 {unknown: invocation outside of NS doesn't evade namespace } -cleanup { namespace delete ::foo } -result {{::foo:bar:_unknown ::foo::bar xxx} {::foo:bar:_unknown ::foo bar::xxx} {::foo:bar:_unknown :: ::foo::bar::xxx} {::foo:bar:_unknown :: foo::bar::xxx}} +test namespace-52.14 {unknown: invocation outside of NS doesn't evade namespace unknown for command with sub-NS, bug 910d67a229fe7f65} -body { + namespace eval ::foo::bar { + proc _unknown args {list ::foo:bar:_unknown [uplevel {namespace current}] $args} + namespace unknown [namespace current]::_unknown + } + set res {} + lappend res [namespace inscope ::foo::bar {xxx::yyy}] [namespace inscope ::foo {bar::xxx::yyy}] [::foo::bar::xxx::yyy] [namespace inscope :: {foo::bar::xxx::yyy}] + # now with existsing ::foo::bar::xxx, but without unknown handler inside (only parent ::foo::bar has a handler): + namespace eval ::foo::bar::xxx {} + lappend res [namespace inscope ::foo::bar {xxx::yyy}] [namespace inscope ::foo {bar::xxx::yyy}] [::foo::bar::xxx::yyy] [namespace inscope :: {foo::bar::xxx::yyy}] +} -cleanup { + namespace delete ::foo + unset -nocomplain res +} -result [lrepeat 2 \ + {::foo:bar:_unknown ::foo::bar xxx::yyy} {::foo:bar:_unknown ::foo bar::xxx::yyy} {::foo:bar:_unknown :: ::foo::bar::xxx::yyy} {::foo:bar:_unknown :: foo::bar::xxx::yyy} +] # TIP 314 - ensembles with parameters test namespace-53.1 {ensembles: parameters} { -- cgit v0.12 From 19e464fbe00deec5ce205e39dbd61107b00a7a80 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 6 Mar 2024 21:29:40 +0000 Subject: small amend: simpler traversing using ns->parentPtr --- generic/tclBasic.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/generic/tclBasic.c b/generic/tclBasic.c index 78685f0..f8baf1c 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -4741,27 +4741,21 @@ TEOV_NotFound( Namespace *dummyNsPtr; const char *simpleName; - tryParentNS: (void) TclGetNamespaceForQualName(interp, qualName, currNsPtr, TCL_NAMESPACE_ONLY | TCL_FIND_IF_NOT_SIMPLE, &currNsPtr, &dummyNsPtr, &dummyNsPtr, &simpleName); - if ((currNsPtr == NULL) || (simpleName == NULL) || + while ((currNsPtr == NULL) || (simpleName == NULL) || currNsPtr->unknownHandlerPtr == NULL || (currNsPtr->flags & (NS_DYING | NS_DEAD)) ) { /* traverse to alive parent namespace containing handler */ - if (currNsPtr) { - qualName = currNsPtr->fullName; - qualLen = strlen(qualName); - if (qualLen > 2 && memchr(qualName, ':', qualLen)) { - currNsPtr = iPtr->globalNsPtr; - goto tryParentNS; + if (!currNsPtr || !(currNsPtr = currNsPtr->parentPtr)) { + /* fallback to the global unknown */ + currNsPtr = iPtr->globalNsPtr; + if (currNsPtr == NULL) { + Tcl_Panic("TEOV_NotFound: NULL global namespace pointer"); } - } - /* fallback to the global unknown */ - currNsPtr = iPtr->globalNsPtr; - if (currNsPtr == NULL) { - Tcl_Panic("TEOV_NotFound: NULL global namespace pointer"); + break; } } } -- cgit v0.12 From 4fbee3712ec9b889a1f6f5a5e1bd1386a661edc7 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 6 Mar 2024 21:44:10 +0000 Subject: core review --- generic/tclBasic.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/generic/tclBasic.c b/generic/tclBasic.c index f8baf1c..41dbee2 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -4744,12 +4744,15 @@ TEOV_NotFound( (void) TclGetNamespaceForQualName(interp, qualName, currNsPtr, TCL_NAMESPACE_ONLY | TCL_FIND_IF_NOT_SIMPLE, &currNsPtr, &dummyNsPtr, &dummyNsPtr, &simpleName); - while ((currNsPtr == NULL) || (simpleName == NULL) || - currNsPtr->unknownHandlerPtr == NULL || + if ((currNsPtr == NULL) || (simpleName == NULL)) { + goto globNS; + } + while (currNsPtr->unknownHandlerPtr == NULL || (currNsPtr->flags & (NS_DYING | NS_DEAD)) ) { /* traverse to alive parent namespace containing handler */ - if (!currNsPtr || !(currNsPtr = currNsPtr->parentPtr)) { + if (!(currNsPtr = currNsPtr->parentPtr)) { + globNS: /* fallback to the global unknown */ currNsPtr = iPtr->globalNsPtr; if (currNsPtr == NULL) { -- cgit v0.12 From e649d3487899b2a23aad3169224a161891e6ae33 Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 8 Mar 2024 13:40:57 +0000 Subject: namespace unknown considers also alternate search path (relative global NS), see namespace-52.14 --- generic/tclBasic.c | 27 +++++++++++++++++++++------ tests/namespace.test | 17 +++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/generic/tclBasic.c b/generic/tclBasic.c index d87fea6..9b4161a 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -4816,20 +4816,35 @@ TEOV_NotFound( * first try to find namespace unknown handler of the namespace * of executed command if available: */ - Namespace *dummyNsPtr; + Namespace *altNsPtr, *dummyNsPtr; const char *simpleName; (void) TclGetNamespaceForQualName(interp, qualName, currNsPtr, - TCL_NAMESPACE_ONLY | TCL_FIND_IF_NOT_SIMPLE, &currNsPtr, - &dummyNsPtr, &dummyNsPtr, &simpleName); - if ((currNsPtr == NULL) || (simpleName == NULL)) { - goto globNS; + TCL_FIND_IF_NOT_SIMPLE, &currNsPtr, &altNsPtr, + &dummyNsPtr, &simpleName); + if (!simpleName) { + goto globNS; + } + if (!currNsPtr || (currNsPtr == iPtr->globalNsPtr)) { + if (!altNsPtr || (altNsPtr == iPtr->globalNsPtr)) { + goto globNS; + } + currNsPtr = altNsPtr; } while (currNsPtr->unknownHandlerPtr == NULL || (currNsPtr->flags & (NS_DYING | NS_DEAD)) ) { /* traverse to alive parent namespace containing handler */ - if (!(currNsPtr = currNsPtr->parentPtr)) { + if (!(currNsPtr = currNsPtr->parentPtr) || + (currNsPtr == iPtr->globalNsPtr) + ) { + /* continue from alternate NS if available */ + if (!altNsPtr || (altNsPtr == iPtr->globalNsPtr)) { + goto globNS; + } + currNsPtr = altNsPtr; + altNsPtr = NULL; + continue; globNS: /* fallback to the global unknown */ currNsPtr = iPtr->globalNsPtr; diff --git a/tests/namespace.test b/tests/namespace.test index 9976cf3..abe642e 100644 --- a/tests/namespace.test +++ b/tests/namespace.test @@ -3149,6 +3149,23 @@ test namespace-52.14 {unknown: invocation outside of NS doesn't evade namespace } -result [lrepeat 2 \ {::foo:bar:_unknown ::foo::bar xxx::yyy} {::foo:bar:_unknown ::foo bar::xxx::yyy} {::foo:bar:_unknown :: ::foo::bar::xxx::yyy} {::foo:bar:_unknown :: foo::bar::xxx::yyy} ] +test namespace-52.14 {unknown: it must consider alternate search path (relative global NS), bug 910d67a229fe7f65} -body { + namespace eval ::foo::bar {} + namespace eval ::xxx::yyy { + proc _unknown args {list ::xxx:yyy:_unknown [uplevel {namespace current}] $args} + namespace unknown [namespace current]::_unknown + } + set res {} + lappend res [namespace inscope ::foo::bar {xxx::yyy::cmd}] [namespace inscope ::foo {xxx::yyy::cmd}] + namespace eval ::foo::bar::xxx {} + lappend res [namespace inscope ::foo::bar {xxx::yyy::cmd}] [namespace inscope ::foo {xxx::yyy::cmd}] + namespace eval ::foo::bar::xxx::yyy {} + lappend res [namespace inscope ::foo::bar {xxx::yyy::cmd}] [namespace inscope ::foo {xxx::yyy::cmd}] +} -cleanup { + namespace delete ::foo + namespace delete ::xxx + unset -nocomplain res +} -result [lrepeat 3 {::xxx:yyy:_unknown ::foo::bar xxx::yyy::cmd} {::xxx:yyy:_unknown ::foo xxx::yyy::cmd}] # TIP 314 - ensembles with parameters test namespace-53.1 {ensembles: parameters} { -- cgit v0.12