Index: generic/tclPathObj.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclPathObj.c,v
retrieving revision 1.66.2.8
diff -u -r1.66.2.8 tclPathObj.c
--- generic/tclPathObj.c	18 Aug 2009 14:43:58 -0000	1.66.2.8
+++ generic/tclPathObj.c	20 Aug 2009 14:49:16 -0000
@@ -20,6 +20,7 @@
  * Prototypes for functions defined later in this file.
  */
 
+static Tcl_Obj *	AppendPath(Tcl_Obj *head, Tcl_Obj *tail);
 static void		DupFsPathInternalRep(Tcl_Obj *srcPtr,
 			    Tcl_Obj *copyPtr);
 static void		FreeFsPathInternalRep(Tcl_Obj *pathPtr);
@@ -1286,6 +1287,30 @@
     const char *p;
     int state = 0, count = 0;
 
+    /* [Bug 2806250] - this is only a partial solution of the problem.
+     * The PATHFLAGS != 0 representation assumes in many places that
+     * the "tail" part stored in the normPathPtr field is itself a
+     * relative path.  Strings that begin with "~" are not relative paths,
+     * so we must prevent their storage in the normPathPtr field.
+     *
+     * More generally we ought to be testing "addStrRep" for any value
+     * that is not a relative path, but in an unconstrained VFS world
+     * that could be just about anything, and testing could be expensive.
+     * Since this routine plays a big role in [glob], anything that slows
+     * it down would be unwelcome.  For now, continue the risk of further
+     * bugs when some Tcl_Filesystem uses otherwise relative path strings
+     * as absolute path strings.  Sensible Tcl_Filesystems will avoid
+     * that by mounting on path prefixes like foo:// which cannot be the
+     * name of a file or directory read from a native [glob] operation.
+     */
+    if (addStrRep[0] == '~') {
+	Tcl_Obj *tail = Tcl_NewStringObj(addStrRep, len);
+
+	pathPtr = AppendPath(dirPtr, tail);
+	Tcl_DecrRefCount(tail);
+	return pathPtr;
+    }
+
     tsdPtr = TCL_TSD_INIT(&tclFsDataKey);
 
     pathPtr = Tcl_NewObj();
@@ -1351,6 +1376,49 @@
 
     return pathPtr;
 }
+
+static Tcl_Obj *
+AppendPath(
+    Tcl_Obj *head,
+    Tcl_Obj *tail)
+{
+    int numBytes;
+    const char *bytes;
+    Tcl_Obj *copy = Tcl_DuplicateObj(head);
+
+    bytes = Tcl_GetStringFromObj(copy, &numBytes);
+
+    /*
+     * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the
+     * Windows special case? Perhaps we should just check if cwd is a root
+     * volume. We should never get numBytes == 0 in this code path.
+     */
+
+    switch (tclPlatform) {
+    case TCL_PLATFORM_UNIX:
+	if (bytes[numBytes-1] != '/') {
+	    Tcl_AppendToObj(copy, "/", 1);
+	}
+	break;
+
+    case TCL_PLATFORM_WINDOWS:
+	/*
+	 * We need the extra 'numBytes != 2', and ':' checks because a volume
+	 * relative path doesn't get a '/'. For example 'glob C:*cat*.exe'
+	 * will return 'C:cat32.exe'
+	 */
+
+	if (bytes[numBytes-1] != '/' && bytes[numBytes-1] != '\\') {
+	    if (numBytes!= 2 || bytes[1] != ':') {
+		Tcl_AppendToObj(copy, "/", 1);
+	    }
+	}
+	break;
+    }
+
+    Tcl_AppendObjToObj(copy, tail);
+    return copy;
+}
 
 /*
  *---------------------------------------------------------------------------
@@ -1364,11 +1432,6 @@
  *	directory. Returns a Tcl_Obj representing filename of the path
  *	relative to the directory.
  *
- *	In the case where the resulting path would start with a '~', we take
- *	special care to return an ordinary string. This means to use that path
- *	(and not have it interpreted as a user name), one must prepend './'.
- *	This may seem strange, but that is how 'glob' is currently defined.
- *
  * Results:
  *	NULL on error, otherwise a valid object, typically with refCount of
  *	zero, which it is assumed the caller will increment.
@@ -1396,6 +1459,26 @@
 		&& fsPathPtr->cwdPtr == cwdPtr) {
 	    pathPtr = fsPathPtr->normPathPtr;
 
+	    /* TODO: Determine how much, if any, of this forcing
+	     * the relative path tail into the "path" Tcl_ObjType
+	     * with a recorded cwdPtr context has any actual value.
+	     *
+	     * Nothing is getting cached.  Not normPathPtr, not nativePathPtr,
+	     * nor fsRecPtr, so storing the cwdPtr context against which such
+	     * cached values might later be validated appears to be of no
+	     * value.  Take that away, and all this code is just a mildly
+	     * optimized equivalent of a call to SetFsPathFromAny().  That
+	     * optimization may have some value, *if* these value in fact
+	     * get used as "path" values before used as something else.
+	     * If not, though, whatever cost we pay below to convert to
+	     * one of the "path" intreps is just a waste, it seems.  The
+	     * usual convention in the core is to delay ObjType conversion
+	     * until it is needed and demanded, and I don't see why this
+	     * section of code should be an exception to that.  Leaving it
+	     * in place for the rest of the 8.5.* releases just for sake
+	     * of stability.
+	     */
+
 	    /*
 	     * Free old representation.
 	     */
@@ -1419,16 +1502,6 @@
 	     * Now pathPtr is a string object.
 	     */
 
-	    if (Tcl_GetString(pathPtr)[0] == '~') {
-		/*
-		 * If the first character of the path is a tilde, we must just
-		 * return the path as is, to agree with the defined behaviour
-		 * of 'glob'.
-		 */
-
-		return pathPtr;
-	    }
-
 	    fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath));
 
 	    /*
@@ -1796,7 +1869,6 @@
 	Tcl_Obj *dir, *copy;
 	int cwdLen;
 	int pathType;
-	const char *cwdStr;
 	ClientData clientData = NULL;
 
 	pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr);
@@ -1804,40 +1876,21 @@
 	if (dir == NULL) {
 	    return NULL;
 	}
+	/* TODO: Figure out why this is needed. */
 	if (pathPtr->bytes == NULL) {
 	    UpdateStringOfFsPath(pathPtr);
 	}
-	copy = Tcl_DuplicateObj(dir);
-	Tcl_IncrRefCount(copy);
+
+	copy = AppendPath(dir, fsPathPtr->normPathPtr);
 	Tcl_IncrRefCount(dir);
+	Tcl_IncrRefCount(copy);
 
 	/*
 	 * We now own a reference on both 'dir' and 'copy'
 	 */
 
-	cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
-
-	/*
-	 * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about
-	 * the Windows special case? Perhaps we should just check if cwd is a
-	 * root volume. We should never get cwdLen == 0 in this code path.
-	 */
-
-	switch (tclPlatform) {
-	case TCL_PLATFORM_UNIX:
-	    if (cwdStr[cwdLen-1] != '/') {
-		Tcl_AppendToObj(copy, "/", 1);
-		cwdLen++;
-	    }
-	    break;
-	case TCL_PLATFORM_WINDOWS:
-	    if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') {
-		Tcl_AppendToObj(copy, "/", 1);
-		cwdLen++;
-	    }
-	    break;
-	}
-	Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
+	(void) Tcl_GetStringFromObj(dir, &cwdLen);
+	cwdLen += (Tcl_GetString(copy)[cwdLen] == '/');
 
 	/* Normalize the combined string. */
 
@@ -1937,35 +1990,12 @@
 	} else if (fsPathPtr->normPathPtr == NULL) {
 	    int cwdLen;
 	    Tcl_Obj *copy;
-	    const char *cwdStr;
 	    ClientData clientData = NULL;
 
-	    copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
-	    Tcl_IncrRefCount(copy);
-	    cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
+	    copy = AppendPath(fsPathPtr->cwdPtr, pathPtr);
 
-	    /*
-	     * Should we perhaps use 'Tcl_FSPathSeparator'? But then what
-	     * about the Windows special case? Perhaps we should just check if
-	     * cwd is a root volume. We should never get cwdLen == 0 in this
-	     * code path.
-	     */
-
-	    switch (tclPlatform) {
-	    case TCL_PLATFORM_UNIX:
-		if (cwdStr[cwdLen-1] != '/') {
-		    Tcl_AppendToObj(copy, "/", 1);
-		    cwdLen++;
-		}
-		break;
-	    case TCL_PLATFORM_WINDOWS:
-		if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') {
-		    Tcl_AppendToObj(copy, "/", 1);
-		    cwdLen++;
-		}
-		break;
-	    }
-	    Tcl_AppendObjToObj(copy, pathPtr);
+	    (void) Tcl_GetStringFromObj(fsPathPtr->cwdPtr, &cwdLen);
+	    cwdLen += (Tcl_GetString(copy)[cwdLen] == '/');
 
 	    /*
 	     * Normalize the combined string, but only starting after the end
@@ -2716,7 +2746,6 @@
     register Tcl_Obj *pathPtr)	/* path obj with string rep to update. */
 {
     FsPath *fsPathPtr = PATHOBJ(pathPtr);
-    const char *cwdStr;
     int cwdLen;
     Tcl_Obj *copy;
 
@@ -2724,42 +2753,8 @@
 	Tcl_Panic("Called UpdateStringOfFsPath with invalid object");
     }
 
-    copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
-    Tcl_IncrRefCount(copy);
-
-    cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
-
-    /*
-     * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the
-     * Windows special case? Perhaps we should just check if cwd is a root
-     * volume. We should never get cwdLen == 0 in this code path.
-     */
-
-    switch (tclPlatform) {
-    case TCL_PLATFORM_UNIX:
-	if (cwdStr[cwdLen-1] != '/') {
-	    Tcl_AppendToObj(copy, "/", 1);
-	    cwdLen++;
-	}
-	break;
-
-    case TCL_PLATFORM_WINDOWS:
-	/*
-	 * We need the extra 'cwdLen != 2', and ':' checks because a volume
-	 * relative path doesn't get a '/'. For example 'glob C:*cat*.exe'
-	 * will return 'C:cat32.exe'
-	 */
-
-	if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') {
-	    if (cwdLen != 2 || cwdStr[1] != ':') {
-		Tcl_AppendToObj(copy, "/", 1);
-		cwdLen++;
-	    }
-	}
-	break;
-    }
+    copy = AppendPath(fsPathPtr->cwdPtr, fsPathPtr->normPathPtr);
 
-    Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
     pathPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen);
     pathPtr->length = cwdLen;
     copy->bytes = tclEmptyStringRep;
