Tcl Source Code

Artifact [c6dd4c08e1]
Login

Artifact c6dd4c08e1f9a451552759aa9850a910e87098f9:

Attachment "cmdResVsCmdLiterals.patch" to ticket [3418547fff] added by foxcruiser 2011-10-04 23:15:23.
Index: generic/tclBasic.c
===================================================================
--- generic/tclBasic.c
+++ generic/tclBasic.c
@@ -1920,10 +1920,20 @@
         Tcl_SetErrorCode(interp, "TCL", "EXPOSE", "COMMAND_EXISTS", NULL);
 	return TCL_ERROR;
     }
 
     /*
+     * Command resolvers (per-interp, per-namespace) might have resolved to a
+     * command for the given namespace scope with this command not being
+     * registered with the namespace's command table. During BC compilation,
+     * the so-resolved command turns into a CmdName literal. Without
+     * invalidating a possible CmdName literal here explicitly, such literals
+     * keep being reused while pointing to overhauled commands.
+     */
+    TclInvalidateCmdLiteral(interp, cmdName, nsPtr);
+
+    /*
      * The list of command exported from the namespace might have changed.
      * However, we do not need to recompute this just yet; next time we need
      * the info will be soon enough.
      */
 
@@ -2066,10 +2076,21 @@
 	     */
 
 	    ckfree(Tcl_GetHashValue(hPtr));
 	}
     } else {
+        
+        /*
+         * Command resolvers (per-interp, per-namespace) might have resolved to a
+         * command for the given namespace scope with this command not being
+         * registered with the namespace's command table. During BC compilation,
+         * the so-resolved command turns into a CmdName literal. Without
+         * invalidating a possible CmdName literal here explicitly, such literals
+         * keep being reused while pointing to overhauled commands.
+         */
+        TclInvalidateCmdLiteral(interp, tail, nsPtr);
+
 	/*
 	 * The list of command exported from the namespace might have changed.
 	 * However, we do not need to recompute this just yet; next time we
 	 * need the info will be soon enough.
 	 */
@@ -2239,10 +2260,20 @@
 	     */
 
 	    ckfree(Tcl_GetHashValue(hPtr));
 	}
     } else {
+        /*
+         * Command resolvers (per-interp, per-namespace) might have resolved to a
+         * command for the given namespace scope with this command not being
+         * registered with the namespace's command table. During BC compilation,
+         * the so-resolved command turns into a CmdName literal. Without
+         * invalidating a possible CmdName literal here explicitly, such literals
+         * keep being reused while pointing to overhauled commands.
+         */
+        TclInvalidateCmdLiteral(interp, tail, nsPtr);
+
 	/*
 	 * The list of command exported from the namespace might have changed.
 	 * However, we do not need to recompute this just yet; next time we
 	 * need the info will be soon enough.
 	 */
@@ -2547,10 +2578,21 @@
      * but that's no big deal.
      */
 
     TclInvalidateNsCmdLookup(cmdNsPtr);
     TclInvalidateNsCmdLookup(cmdPtr->nsPtr);
+
+    
+    /*
+     * Command resolvers (per-interp, per-namespace) might have resolved to a
+     * command for the given namespace scope with this command not being
+     * registered with the namespace's command table. During BC compilation,
+     * the so-resolved command turns into a CmdName literal. Without
+     * invalidating a possible CmdName literal here explicitly, such literals
+     * keep being reused while pointing to overhauled commands.
+     */
+    TclInvalidateCmdLiteral(interp, newTail, cmdPtr->nsPtr);
 
     /*
      * Script for rename traces can delete the command "oldName". Therefore
      * increment the reference count for cmdPtr so that it's Command structure
      * is freed only towards the end of this function by calling

Index: generic/tclCompile.h
===================================================================
--- generic/tclCompile.h
+++ generic/tclCompile.h
@@ -953,10 +953,12 @@
 			    const char *string, int maxChars);
 MODULE_SCOPE void	TclRegisterAuxDataType(const AuxDataType *typePtr);
 MODULE_SCOPE int	TclRegisterLiteral(CompileEnv *envPtr,
 			    char *bytes, int length, int flags);
 MODULE_SCOPE void	TclReleaseLiteral(Tcl_Interp *interp, Tcl_Obj *objPtr);
+MODULE_SCOPE void	TclInvalidateCmdLiteral(Tcl_Interp *interp, 
+						CONST char *name, Namespace *nsPtr);
 MODULE_SCOPE int	TclSingleOpCmd(ClientData clientData,
 			    Tcl_Interp *interp, int objc,
 			    Tcl_Obj *const objv[]);
 MODULE_SCOPE int	TclSortingOpCmd(ClientData clientData,
 			    Tcl_Interp *interp, int objc,

Index: generic/tclLiteral.c
===================================================================
--- generic/tclLiteral.c
+++ generic/tclLiteral.c
@@ -934,10 +934,52 @@
     if (oldBuckets != tablePtr->staticBuckets) {
 	ckfree(oldBuckets);
     }
 }
 
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclInvalidateCmdLiteral --
+ *
+ *	Invalidate a command literal entry, if present in the literal hash
+ *	tables, by resetting its internal representation. This invalidation
+ *	leaves it in the literal tables and in existing literal arrays. As a
+ *	result, existing references continue to work but we force a fresh
+ *	command look-up upon the next use (see, in particular,
+ *	TclSetCmdNameObj()).
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects: 
+ *	Resets the internal representation of the CmdName Tcl_Obj
+ *	using TclFreeIntRep().
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclInvalidateCmdLiteral(
+    Tcl_Interp *interp,		/* Interpreter for which to invalidate a
+				   command literal */
+    CONST char *name,		/* Points to the start of the cmd literal name */
+    Namespace *nsPtr)		/* The namespace for which to lookup and
+				   invalidate a cmd literal */
+{
+    Interp *iPtr = (Interp *)interp;
+    Tcl_Obj *literalObjPtr;
+    
+    literalObjPtr = TclCreateLiteral(iPtr, (char *)name, strlen(name), -1, NULL, 
+                                     nsPtr, 0, NULL);
+    if (literalObjPtr && literalObjPtr->typePtr == &tclCmdNameType) {
+        TclFreeIntRep(literalObjPtr);
+    }
+}
+
+
 #ifdef TCL_COMPILE_STATS
 /*
  *----------------------------------------------------------------------
  *
  * TclLiteralStats --

Index: generic/tclTest.c
===================================================================
--- generic/tclTest.c
+++ generic/tclTest.c
@@ -409,10 +409,14 @@
 			    Tcl_Interp *interp, int objc,
 			    Tcl_Obj *const objv[]);
 static int		TestNRELevels(ClientData clientData,
 			    Tcl_Interp *interp, int objc,
 			    Tcl_Obj *const objv[]);
+static int		TestInterpResolversCmd(ClientData clientData,
+			    Tcl_Interp *interp, int objc,
+			    Tcl_Obj *const objv[]);
+
 
 static const Tcl_Filesystem testReportingFilesystem = {
     "reporting",
     sizeof(Tcl_Filesystem),
     TCL_FILESYSTEM_VERSION_1,
@@ -673,10 +677,14 @@
     Tcl_CreateMathFunc(interp, "T3", 2, t3ArgTypes, TestMathFunc2,
 	    NULL);
 
     Tcl_CreateObjCommand(interp, "testnrelevels", TestNRELevels,
 	    NULL, NULL);
+
+    Tcl_CreateObjCommand(interp, "testinterpresolver", TestInterpResolversCmd,
+	    NULL, NULL);
+
 
     if (TclObjTest_Init(interp) != TCL_OK) {
 	return TCL_ERROR;
     }
     if (Procbodytest_Init(interp) != TCL_OK) {
@@ -7124,10 +7132,196 @@
     result[2] = Tcl_NewListObj(count, remObjv);
     Tcl_SetObjResult(interp, Tcl_NewListObj(3, result));
     ckfree(remObjv);
     return TCL_OK;
 }
+ 
+ 
+static int
+InterpCmdResolver(Tcl_Interp *interp, 
+                  CONST84 char *name,
+                  Tcl_Namespace *context, 
+                  int flags, 
+                  Tcl_Command *rPtr) 
+{
+    Tcl_Command sourceCmdPtr;
+    Interp *iPtr = (Interp *)interp;
+    CallFrame *varFramePtr = iPtr->varFramePtr;
+    Proc *procPtr = (varFramePtr->isProcCallFrame & FRAME_IS_PROC) ? 
+        varFramePtr->procPtr : NULL;
+    Namespace *ns2NsPtr;
+    
+    ns2NsPtr = Tcl_FindNamespace(interp, "::ns2", NULL, 0);
+ 
+    if (procPtr && (procPtr->cmdPtr->nsPtr == iPtr->globalNsPtr || (ns2NsPtr && procPtr->cmdPtr->nsPtr == ns2NsPtr))) {
+        CONST char *callingCmdName = Tcl_GetCommandName(interp, (Tcl_Command)procPtr->cmdPtr);
+ 	
+        if (*callingCmdName == 'x' && *(callingCmdName + 1) == '\0' && 
+            *name == 'z' && *(name + 1) == '\0') {
+            sourceCmdPtr = Tcl_FindCommand(interp, "y", NULL, TCL_GLOBAL_ONLY);
+            if (sourceCmdPtr != NULL) {
+ 
+ 		/*fprintf(stdout, "InterpCmdResolver resolves cmd %s as '::y' in proc 'x'\n", name);*/
+ 
+                *rPtr = sourceCmdPtr;
+                return TCL_OK;
+            }
+        }
+    }
+    return TCL_CONTINUE;
+}
+ 
+static int
+InterpVarResolver(Tcl_Interp *interp, 
+                  CONST84 char *name,
+                  Tcl_Namespace *context, 
+                  int flags, 
+                  Tcl_Var *rPtr) 
+{
+    return TCL_CONTINUE;
+}
+ 
+typedef struct MyResolvedVarInfo {
+    Tcl_ResolvedVarInfo vInfo;        /* This must be the first element. */
+    Tcl_Var var;
+    Tcl_Obj *nameObj;
+} MyResolvedVarInfo;
+ 
+ 
+static void
+HashVarFree(Tcl_Var var) {
+    if (VarHashRefCount(var) < 2) {
+        ckfree((char *) var);
+    } else {
+        VarHashRefCount(var)--;
+    }
+}
+ 
+ 
+static void
+MyCompiledVarFree(Tcl_ResolvedVarInfo *vInfoPtr) {
+    MyResolvedVarInfo *resVarInfo = (MyResolvedVarInfo *)vInfoPtr;
+ 
+    /*fprintf(stderr, "MyCompiledVarFree %p for variable '%s'\n", 
+      resVarInfo, Tcl_GetString(resVarInfo->nameObj));*/
+ 
+    Tcl_DecrRefCount(resVarInfo->nameObj);
+    if (resVarInfo->var) {
+        HashVarFree(resVarInfo->var);
+    }
+    ckfree((char *)vInfoPtr);
+}
+ 
+#define TclVarHashGetValue(hPtr)	((Var *) ((char *)hPtr - TclOffset(VarInHash, entry)))
+ 
+static Tcl_Var
+MyCompiledVarFetch(Tcl_Interp *interp, Tcl_ResolvedVarInfo *vinfoPtr) {
+    MyResolvedVarInfo *resVarInfo = (MyResolvedVarInfo *)vinfoPtr;
+    Tcl_Var var = resVarInfo->var;
+    Namespace *nsPtr;
+    int isNewVar;
+    Interp *iPtr = (Interp *) interp;
+    Tcl_HashEntry *hPtr;
+ 
+    /*
+      int flags = var ? ((Var *)var)->flags : 0;
+      fprintf(stderr,"MyCompiledVarFetch var '%s' var %p flags = %.4x dead? %.4x\n",
+      Tcl_GetString(resVarInfo->nameObj), var, flags, flags & VAR_DEAD_HASH);*/
+ 
+    if (var && (((((Var *)var)->flags) & VAR_DEAD_HASH) == 0)) {
+        /*
+         * The cached variable is valid, return it.
+         */
+        /*fprintf(stderr, ".... use cached var '%s' var %p flags = %.4x\n",
+          Tcl_GetString(resVarInfo->nameObj), var, ((Var *)var)->flags);*/
+        return var;
+    }
+ 
+    if (var) {
+        /*
+         * The variable is not valid anymore. Clean it up.
+         */
+        HashVarFree(var);
+    }
+ 
+    nsPtr = iPtr->globalNsPtr;
+ 
+    hPtr = Tcl_CreateHashEntry((Tcl_HashTable *)&(nsPtr->varTable),
+                               (char *) resVarInfo->nameObj, &isNewVar);
+    if (hPtr) {
+        var = (Tcl_Var)TclVarHashGetValue(hPtr);
+    } else {
+        var = NULL;
+    }
+   
+    resVarInfo->var = var;
+    /*
+     * Increment the reference counter to avoid ckfree() of the variable
+     * in Tcl's FreeVarEntry(); for cleanup, we provide our own
+     * HashVarFree();
+     */
+    VarHashRefCount(var);
+ 
+    return var;
+}
+ 
+ 
+static int
+InterpCompiledVarResolver(Tcl_Interp *interp,
+                          CONST84 char *name, 
+                          int length, 
+                          Tcl_Namespace *context,
+                          Tcl_ResolvedVarInfo **rPtr)
+{
+    /*fprintf(stderr, "InterpCompiledVarResolver for '%s'\n", name);*/
+    /* special handling of variables starting with a capital T */
+    if (*name == 'T') {
+ 	MyResolvedVarInfo *resVarInfo = ckalloc(sizeof(MyResolvedVarInfo));
+ 	/*fprintf(stderr, "alloc MyResolvedVarInfo %p\n", resVarInfo);*/
+ 	resVarInfo->vInfo.fetchProc = MyCompiledVarFetch;
+ 	resVarInfo->vInfo.deleteProc = MyCompiledVarFree; /* if NULL, Tcl does a ckfree on proc clean up */
+ 	resVarInfo->var = NULL;
+ 	resVarInfo->nameObj = Tcl_NewStringObj(name, -1);
+ 	Tcl_IncrRefCount(resVarInfo->nameObj);
+ 	*rPtr = (Tcl_ResolvedVarInfo *)resVarInfo;
+ 	return TCL_OK;
+    }
+    return TCL_CONTINUE;
+}
+ 
+static int 
+TestInterpResolversCmd(ClientData clientData,
+                       Tcl_Interp *interp, 
+                       int objc,
+                       Tcl_Obj *const objv[]) 
+{
+    CONST char *option;
+    if (objc != 2) {
+ 	Tcl_AppendResult(interp, "wrong # arguments: should be \"", TclGetString(objv[0]),
+                         " up|down\"", NULL);
+ 	return TCL_ERROR;
+    }
+    option = TclGetString(objv[1]);
+    if (*option == 'u' && strcmp(option, "up") == 0) {
+        Tcl_AddInterpResolvers(interp,"interpResolver",
+                               InterpCmdResolver,
+                               InterpVarResolver,
+                               InterpCompiledVarResolver);
+    } else if (*option == 'd' && strcmp(option, "down") == 0) {
+        if(Tcl_RemoveInterpResolvers(interp, "interpResolver") == 0) {
+            Tcl_AppendResult(interp, "could not remove the resolver scheme", NULL);
+            return TCL_ERROR;
+        }
+    } else {
+ 	Tcl_AppendResult(interp, "bad option \"", option,
+                         "\": must be 'up' or 'down'", NULL);
+ 	return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+
 
 /*
  * Local Variables:
  * mode: c
  * c-basic-offset: 4

ADDED    tests/resolvers.test
Index: tests/resolvers.test
===================================================================
--- tests/resolvers.test
+++ tests/resolvers.test
@@ -0,0 +1,220 @@
+#
+# This test collection covers some unwanted interactions between
+# command literal sharing and the use of command resolvers
+# (per-interp) which cause command literals to be re-used with their
+# command references being invalid in the reusing context.
+#
+# [email protected]
+# [email protected]
+#
+
+if {[lsearch [namespace children] ::tcltest] == -1} {
+    package require tcltest
+    namespace import -force ::tcltest::*
+}
+
+test lit-1.1 {cmdNameObj sharing vs. cmd resolver: namespace import} -setup {
+  testinterpresolver up
+
+  namespace eval ::ns1 {
+    proc z {} { return Z }
+    namespace export z
+  }
+  proc ::y {} { return Y }
+  
+  proc ::x {} {
+    z
+  }
+
+} -body {
+  # 1) Have the proc body compiled: During compilation or,
+  # alternatively, the first evaluation of the compiled body, the
+  # InterpCmdResolver (see tclTest.c) maps the cmd token "z" to "::y";
+  # this mapping is saved in the resulting CmdName Tcl_Obj with the
+  # print string "z". The CmdName Tcl_Obj is turned into a command
+  # literal shared for a given (here: the global) namespace.
+  set r0 [x]; # --> The result of [x] is "Y"
+  # 2) After having requested cmd resolution above, we can now use
+  # the globally shared CmdName Tcl_Obj "z", now bound to cmd
+  # ::y. This is certainly questionable, but defensible
+  set r1 [z]; # # --> The result of [z] is "Y"
+  # 3) We import from the namespace ns1 another z. [namespace import]
+  # takes care "shadowed" cmd references, however, till now cmd
+  # literals have not been touched. This is, however, necessary since
+  # the BC compiler (used in the [namespace eval]) seems to be eager
+  # to reuse CmdName Tcl_Objs as cmd literals for a given NS scope. We
+  # expect, that r2 is "Z", the result of the namespace imported cmd.
+  namespace eval :: {
+    namespace import ::ns1::z
+    set r2 [z]
+  }
+  list $r0 $r1 $r2
+} -cleanup {
+  testinterpresolver down
+  rename ::x ""
+  rename ::y ""
+  namespace delete ::ns1
+} -constraints {
+} -result {Y Y Z}
+
+
+test lit-1.2 {cmdNameObj sharing vs. cmd resolver: proc creation} -setup {
+  testinterpresolver up
+
+  proc ::y {} { return Y }
+  
+  proc ::x {} {
+    z
+  }
+
+} -body {
+  set r0 [x]
+  set r1 [z]
+  proc ::foo {} {
+    proc ::z {} { return Z }
+    return [z]
+  }
+  list $r0 $r1 [::foo]
+} -cleanup {
+  testinterpresolver down
+  rename ::x ""
+  rename ::y ""
+  rename ::foo ""
+  rename ::z ""
+} -result {Y Y Z}
+
+test lit-1.3 {cmdNameObj sharing vs. cmd resolver: rename} -setup {
+  testinterpresolver up
+  proc ::Z {} { return Z }
+
+  proc ::y {} { return Y }
+  
+  proc ::x {} {
+    z
+  }
+
+} -body {
+  set r0 [x]
+  set r1 [z]
+  namespace eval :: {
+    rename ::Z ::z
+    set r2 [z]
+  }
+  list $r0 $r1 $r2
+} -cleanup {
+  testinterpresolver down
+  rename ::x ""
+  rename ::y ""
+  rename ::z ""
+} -result {Y Y Z}
+
+test lit-1.4 {cmdNameObj sharing vs. cmd resolver: interp expose} -setup {
+  testinterpresolver up
+  proc ::Z {} { return Z }
+  interp hide {} Z
+
+  proc ::y {} { return Y }
+  
+  proc ::x {} {
+    z
+  }
+
+} -body {
+  set r0 [x]
+  set r1 [z]
+  interp expose {} Z z
+  namespace eval :: {
+    set r2 [z]
+  }
+  list $r0 $r1 $r2
+} -cleanup {
+  testinterpresolver down
+  rename ::x ""
+  rename ::y ""
+  rename ::z ""
+} -result {Y Y Z}
+
+test lit-1.5 {cmdNameObj sharing vs. cmd resolver: other than global NS} -setup {
+  testinterpresolver up
+
+  namespace eval ::ns1 {
+    proc z {} { return Z }
+    namespace export z
+  }
+
+  proc ::y {} { return Y }
+
+  namespace eval ::ns2 {
+    proc x {} {
+      z
+    }
+  }
+
+} -body {
+  
+  set r0 [namespace eval ::ns2 {x}]
+  set r1 [namespace eval ::ns2 {z}]
+  namespace eval ::ns2 {
+    namespace import ::ns1::z
+    set r2 [z]
+  }
+  list $r0 $r1 $r2
+} -cleanup {
+  testinterpresolver down
+  namespace delete ::ns2
+  namespace delete ::ns1
+} -result {Y Y Z}
+
+test lit-1.6 {cmdNameObj sharing vs. cmd resolver: interp alias} -setup {
+  testinterpresolver up
+  proc ::Z {} { return Z }
+
+  proc ::y {} { return Y }
+  
+  proc ::x {} {
+    z
+  }
+
+} -body {
+  set r0 [x]
+  set r1 [z]
+  namespace eval :: {
+    interp alias {} ::z {} ::Z
+    set r2 [z]
+  }
+  list $r0 $r1 $r2
+} -cleanup {
+  testinterpresolver down
+  rename ::x ""
+  rename ::y ""
+  rename ::Z ""
+} -result {Y Y Z}
+
+
+
+test interpRes-1.1 {compiled var resolver: Bug #3383616} -setup {
+  testinterpresolver up
+
+  # The compiled var resolver fetches just variables starting with a
+  # capital "T" and stores some test information in the
+  # resolver-specific resolver var info.
+
+  proc ::x {} {
+    set T1 100
+    return $T1
+  } 
+
+} -body {
+  # Call "x" the first time, causing a byte code compilation of the
+  # body. During the compilation the compiled var resolver, the
+  # resolve-specific var info is allocated, during the execution of
+  # the body, the variable is fetched and cached.
+  x; 
+  # During later calls, the cached variable is reused.
+  x
+  # When the proc is freed, the resolver-specific resolver var info is
+  # freed. This did not happen before fix #3383616.
+  rename ::x ""
+} -cleanup {
+  testinterpresolver down
+} -result {}