diff options
author | dgp <dgp@users.sourceforge.net> | 2017-09-02 21:48:05 (GMT) |
---|---|---|
committer | dgp <dgp@users.sourceforge.net> | 2017-09-02 21:48:05 (GMT) |
commit | 1d88a1ef04b61ff723f9f102bdd25b5e6327b993 (patch) | |
tree | 7d06332b52ff9d4645ae411863a63a055b3f1eb8 | |
parent | 0364abb28794dfc51a043615b44ead2175bf91f4 (diff) | |
parent | 19028a0d17ae987e42a91918abe8421e3030812b (diff) | |
download | tcl-1d88a1ef04b61ff723f9f102bdd25b5e6327b993.zip tcl-1d88a1ef04b61ff723f9f102bdd25b5e6327b993.tar.gz tcl-1d88a1ef04b61ff723f9f102bdd25b5e6327b993.tar.bz2 |
[0e4d88b650] Allow command overwrite when deletion callback deletes namespace.core_8_5_branch
-rw-r--r-- | generic/tclBasic.c | 166 | ||||
-rw-r--r-- | tests/basic.test | 15 |
2 files changed, 119 insertions, 62 deletions
diff --git a/generic/tclBasic.c b/generic/tclBasic.c index 44cf543..7f75892 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -1851,11 +1851,11 @@ Tcl_CreateCommand( { Interp *iPtr = (Interp *) interp; ImportRef *oldRefPtr = NULL; - Namespace *nsPtr, *dummy1, *dummy2; - Command *cmdPtr, *refCmdPtr; + Namespace *nsPtr; + Command *cmdPtr; Tcl_HashEntry *hPtr; const char *tail; - int isNew; + int isNew = 0, deleted = 0; ImportedCmdData *dataPtr; if (iPtr->flags & DELETED) { @@ -1868,32 +1868,52 @@ Tcl_CreateCommand( } /* - * Determine where the command should reside. If its name contains - * namespace qualifiers, we put it in the specified namespace; otherwise, - * we always put it in the global namespace. + * If the command name we seek to create already exists, we need to + * delete that first. That can be tricky in the presence of traces. + * Loop until we no longer find an existing command in the way, or + * until we've deleted one command and that didn't finish the job. */ - if (strstr(cmdName, "::") != NULL) { - TclGetNamespaceForQualName(interp, cmdName, NULL, - TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy1, &dummy2, &tail); - if ((nsPtr == NULL) || (tail == NULL)) { - return (Tcl_Command) NULL; - } - } else { - nsPtr = iPtr->globalNsPtr; - tail = cmdName; - } + while (1) { + /* + * Determine where the command should reside. If its name contains + * namespace qualifiers, we put it in the specified namespace; + * otherwise, we always put it in the global namespace. + */ + + if (strstr(cmdName, "::") != NULL) { + Namespace *dummy1, *dummy2; + + TclGetNamespaceForQualName(interp, cmdName, NULL, + TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy1, &dummy2, &tail); + if ((nsPtr == NULL) || (tail == NULL)) { + return (Tcl_Command) NULL; + } + } else { + nsPtr = iPtr->globalNsPtr; + tail = cmdName; + } - hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); - if (!isNew) { + hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); + + if (isNew || deleted) { + /* + * isNew - No conflict with existing command. + * deleted - We've already deleted a conflicting command + */ + break; + } + + /* An existing command conflicts. Try to delete it.. */ + cmdPtr = Tcl_GetHashValue(hPtr); + /* - * Command already exists. Delete the old one. Be careful to preserve + * Be careful to preserve * any existing import links so we can restore them down below. That * way, you can redefine a command and its import status will remain * intact. */ - cmdPtr = Tcl_GetHashValue(hPtr); cmdPtr->refCount++; if (cmdPtr->importRefPtr) { cmdPtr->flags |= CMD_REDEF_IN_PROGRESS; @@ -1906,18 +1926,21 @@ Tcl_CreateCommand( cmdPtr->importRefPtr = NULL; } TclCleanupCommandMacro(cmdPtr); + deleted = 1; + } - hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); - if (!isNew) { - /* - * If the deletion callback recreated the command, just throw away - * the new command (if we try to delete it again, we could get - * stuck in an infinite loop). - */ + if (!isNew) { + /* + * If the deletion callback recreated the command, just throw away + * the new command (if we try to delete it again, we could get + * stuck in an infinite loop). + */ + + ckfree((char *) Tcl_GetHashValue(hPtr)); + } + + if (!deleted) { - ckfree((char *) Tcl_GetHashValue(hPtr)); - } - } else { /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we @@ -1952,7 +1975,7 @@ Tcl_CreateCommand( if (oldRefPtr != NULL) { cmdPtr->importRefPtr = oldRefPtr; while (oldRefPtr != NULL) { - refCmdPtr = oldRefPtr->importedCmdPtr; + Command *refCmdPtr = oldRefPtr->importedCmdPtr; dataPtr = refCmdPtr->objClientData; dataPtr->realCmdPtr = cmdPtr; oldRefPtr = oldRefPtr->nextPtr; @@ -2013,11 +2036,11 @@ Tcl_CreateObjCommand( { Interp *iPtr = (Interp *) interp; ImportRef *oldRefPtr = NULL; - Namespace *nsPtr, *dummy1, *dummy2; - Command *cmdPtr, *refCmdPtr; + Namespace *nsPtr; + Command *cmdPtr; Tcl_HashEntry *hPtr; const char *tail; - int isNew; + int isNew = 0, deleted = 0; ImportedCmdData *dataPtr; if (iPtr->flags & DELETED) { @@ -2030,28 +2053,44 @@ Tcl_CreateObjCommand( } /* - * Determine where the command should reside. If its name contains - * namespace qualifiers, we put it in the specified namespace; otherwise, - * we always put it in the global namespace. + * If the command name we seek to create already exists, we need to + * delete that first. That can be tricky in the presence of traces. + * Loop until we no longer find an existing command in the way, or + * until we've deleted one command and that didn't finish the job. */ - if (strstr(cmdName, "::") != NULL) { - TclGetNamespaceForQualName(interp, cmdName, NULL, + while (1) { + /* + * Determine where the command should reside. If its name contains + * namespace qualifiers, we put it in the specified namespace; + * otherwise, we always put it in the global namespace. + */ + + if (strstr(cmdName, "::") != NULL) { + Namespace *dummy1, *dummy2; + + TclGetNamespaceForQualName(interp, cmdName, NULL, TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy1, &dummy2, &tail); - if ((nsPtr == NULL) || (tail == NULL)) { - return (Tcl_Command) NULL; - } - } else { - nsPtr = iPtr->globalNsPtr; - tail = cmdName; - } + if ((nsPtr == NULL) || (tail == NULL)) { + return (Tcl_Command) NULL; + } + } else { + nsPtr = iPtr->globalNsPtr; + tail = cmdName; + } - hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); - TclInvalidateNsPath(nsPtr); - if (!isNew) { - cmdPtr = Tcl_GetHashValue(hPtr); + hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); - /* Command already exists. */ + if (isNew || deleted) { + /* + * isNew - No conflict with existing command. + * deleted - We've already deleted a conflicting command + */ + break; + } + + /* An existing command conflicts. Try to delete it.. */ + cmdPtr = Tcl_GetHashValue(hPtr); /* * [***] This is wrong. See Tcl Bug a16752c252. @@ -2089,18 +2128,20 @@ Tcl_CreateObjCommand( cmdPtr->importRefPtr = NULL; } TclCleanupCommandMacro(cmdPtr); + deleted = 1; + } - hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); - if (!isNew) { - /* - * If the deletion callback recreated the command, just throw away - * the new command (if we try to delete it again, we could get - * stuck in an infinite loop). - */ + if (!isNew) { + /* + * If the deletion callback recreated the command, just throw away + * the new command (if we try to delete it again, we could get + * stuck in an infinite loop). + */ - ckfree(Tcl_GetHashValue(hPtr)); - } - } else { + ckfree(Tcl_GetHashValue(hPtr)); + } + + if (!deleted) { /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we @@ -2108,6 +2149,7 @@ Tcl_CreateObjCommand( */ TclInvalidateNsCmdLookup(nsPtr); + TclInvalidateNsPath(nsPtr); } cmdPtr = (Command *) ckalloc(sizeof(Command)); Tcl_SetHashValue(hPtr, cmdPtr); @@ -2134,7 +2176,7 @@ Tcl_CreateObjCommand( if (oldRefPtr != NULL) { cmdPtr->importRefPtr = oldRefPtr; while (oldRefPtr != NULL) { - refCmdPtr = oldRefPtr->importedCmdPtr; + Command *refCmdPtr = oldRefPtr->importedCmdPtr; dataPtr = refCmdPtr->objClientData; dataPtr->realCmdPtr = cmdPtr; oldRefPtr = oldRefPtr->nextPtr; diff --git a/tests/basic.test b/tests/basic.test index 91e4d6c..e4b46fd 100644 --- a/tests/basic.test +++ b/tests/basic.test @@ -221,6 +221,21 @@ test basic-15.1 {Tcl_CreateObjCommand, new cmd goes into a namespace specified i list [test_ns_basic::cmd] \ [namespace delete test_ns_basic] } {::test_ns_basic {}} +test basic-15.2 {Tcl_CreateObjCommand, Bug 0e4d88b650} -setup { + proc deleter {ns args} { + namespace delete $ns + } + namespace eval n { + proc p {} {} + } + trace add command n::p delete [list [namespace which deleter] [namespace current]::n] +} -body { + proc n::p {} {} +} -cleanup { + namespace delete n + rename deleter {} +} + test basic-16.1 {TclInvokeStringCommand} {emptyTest} { } {} |