Index: ChangeLog
===================================================================
RCS file: /cvsroot/tcl/tcl/ChangeLog,v
retrieving revision 1.1811
diff -u -r1.1811 ChangeLog
--- ChangeLog	14 Jan 2004 09:34:32 -0000	1.1811
+++ ChangeLog	14 Jan 2004 21:55:32 -0000
@@ -1,5 +1,10 @@
 2004-01-14  Donal K. Fellows  <donal.k.fellows@man.ac.uk>
 
+	* generic/tclDictObj.c (TraceDictPath, DictExistsCmd): Adjusted
+	behaviour of [dict exists] so a failure to look up a dictionary
+	along the path of dicts doesn't trigger an error.  This is how it
+	was documented to behave previously...  [Bug 871387]
+
 	* generic/tclDictObj.c: Assorted dict fixes from Peter Spjuth
 	relating to [Bug 876170].
 	(SetDictFromAny): Make sure that lists retain their ordering even
Index: generic/tclBinary.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclBinary.c,v
retrieving revision 1.16
diff -u -r1.16 tclBinary.c
--- generic/tclBinary.c	24 Dec 2003 04:18:18 -0000	1.16
+++ generic/tclBinary.c	14 Jan 2004 21:55:37 -0000
@@ -1040,7 +1040,10 @@
 	    cursor = buffer;
 	    arg = 4;
 	    offset = 0;
-	    while (*format != '\0') {
+	    while (1) {
+		if (*format != '\0') {
+		    goto done;
+		}
 		str = format;
 		if (!GetFormatSpec(&format, &cmd, &count)) {
 		    goto done;
Index: generic/tclDictObj.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclDictObj.c,v
retrieving revision 1.13
diff -u -r1.13 tclDictObj.c
--- generic/tclDictObj.c	14 Jan 2004 09:34:33 -0000	1.13
+++ generic/tclDictObj.c	14 Jan 2004 21:55:41 -0000
@@ -15,6 +15,24 @@
 #include "tclInt.h"
 
 /*
+ * Flag values for TraceDictPath().
+ *
+ * DICT_PATH_UPDATE indicates that we are going to be doing an update
+ * at the tip of the path, so duplication of shared objects should be
+ * done along the way.
+ *
+ * DICT_PATH_EXISTS indicates that we are performing an existance test
+ * and a lookup failure should therefore not be an error.  If (and
+ * only if) this flag is set, TraceDictPath() will return the special
+ * value DICT_PATH_NON_EXISTENT if the path is not traceable.
+ */
+
+#define DICT_PATH_UPDATE 1
+#define DICT_PATH_EXISTS 2
+
+#define DICT_PATH_NON_EXISTENT ((Tcl_Obj *) (void *) 1)
+
+/*
  * Prototypes for procedures defined later in this file:
  */
 
@@ -58,7 +76,7 @@
 static void		UpdateStringOfDict _ANSI_ARGS_((Tcl_Obj *dictPtr));
 static Tcl_Obj *	TraceDictPath _ANSI_ARGS_((Tcl_Interp *interp,
 			    Tcl_Obj *rootPtr, int keyc, Tcl_Obj *CONST keyv[],
-			    int willUpdate));
+			    int flags));
 struct Dict;
 static void		DeleteDict _ANSI_ARGS_((struct Dict *dict));
 
@@ -545,25 +563,27 @@
  * Results:
  *	The object at the end of the path, or NULL if there was an error.
  *	Note that this it is an error for an intermediate dictionary on
- *	the path to not exist.
+ *	the path to not exist.  If the flags argument is DICT_PATH_EXISTS,
+ *	a non-existent path gives a DICT_PATH_NON_EXISTENT result.
  *
  * Side effects:
- *	If the willUpdate flag is false, there are no side effects (other
- *	than potential conversion of objects to dictionaries.)  If the
- *	willUpdate flag is true, the following additional side effects
- *	occur.  Shared dictionaries along the path are converted into
- *	unshared objects, and a backward-pointing chain is built using
- *	the chain fields of the dictionaries (for easy invalidation of
- *	string representations.)
+ *	If the flags argument is zero or DICT_PATH_EXISTS, there are
+ *	no side effects (other than potential conversion of objects to
+ *	dictionaries.)  If the flags argument is DICT_PATH_UPDATE, the
+ *	following additional side effects occur.  Shared dictionaries
+ *	along the path are converted into unshared objects, and a
+ *	backward-pointing chain is built using the chain fields of the
+ *	dictionaries (for easy invalidation of string
+ *	representations.)
  *
  *----------------------------------------------------------------------
  */
 
 static Tcl_Obj *
-TraceDictPath(interp, dictPtr, keyc, keyv, willUpdate)
+TraceDictPath(interp, dictPtr, keyc, keyv, flags)
     Tcl_Interp *interp;
     Tcl_Obj *dictPtr, *CONST keyv[];
-    int keyc, willUpdate;
+    int keyc, flags;
 {
     Dict *dict, *newDict;
     int i;
@@ -574,7 +594,7 @@
 	}
     }
     dict = (Dict *) dictPtr->internalRep.otherValuePtr;
-    if (willUpdate) {
+    if (flags == DICT_PATH_UPDATE) {
 	dict->chain = NULL;
     }
 
@@ -583,6 +603,9 @@
 	Tcl_Obj *tmpObj;
 
 	if (hPtr == NULL) {
+	    if (flags == DICT_PATH_EXISTS) {
+		return DICT_PATH_NON_EXISTENT;
+	    }
 	    if (interp != NULL) {
 		Tcl_ResetResult(interp);
 		Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
@@ -599,7 +622,7 @@
 	    }
 	}
 	newDict = (Dict *) tmpObj->internalRep.otherValuePtr;
-	if (willUpdate) {
+	if (flags == DICT_PATH_UPDATE) {
 	    if (Tcl_IsShared(tmpObj)) {
 		Tcl_DecrRefCount(tmpObj);
 		tmpObj = Tcl_DuplicateObj(tmpObj);
@@ -1032,7 +1055,7 @@
 	Tcl_Panic("Tcl_DictObjPutKeyList called with empty key list");
     }
 
-    dictPtr = TraceDictPath(interp, dictPtr, keyc-1, keyv, /*willUpdate*/ 1);
+    dictPtr = TraceDictPath(interp, dictPtr, keyc-1, keyv, DICT_PATH_UPDATE);
     if (dictPtr == NULL) {
 	return TCL_ERROR;
     }
@@ -1088,7 +1111,7 @@
 	Tcl_Panic("Tcl_DictObjRemoveKeyList called with empty key list");
     }
 
-    dictPtr = TraceDictPath(interp, dictPtr, keyc-1, keyv, /*willUpdate*/ 1);
+    dictPtr = TraceDictPath(interp, dictPtr, keyc-1, keyv, DICT_PATH_UPDATE);
     if (dictPtr == NULL) {
 	return TCL_ERROR;
     }
@@ -1320,7 +1343,7 @@
      * going through a chain of searches.)  Note that this loop always
      * executes at least once.
      */
-    dictPtr = TraceDictPath(interp, objv[2], objc-4, objv+3, /*willUpdate*/ 0);
+    dictPtr = TraceDictPath(interp, objv[2], objc-4, objv+3, 0);
     if (dictPtr == NULL) {
 	return TCL_ERROR;
     }
@@ -1618,10 +1641,14 @@
 	return TCL_ERROR;
     }
 
-    dictPtr = TraceDictPath(interp, objv[2], objc-4, objv+3, /*willUpdate*/ 0);
+    dictPtr = TraceDictPath(interp, objv[2], objc-4, objv+3, DICT_PATH_EXISTS);
     if (dictPtr == NULL) {
 	return TCL_ERROR;
     }
+    if (dictPtr == DICT_PATH_NON_EXISTENT) {
+	Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0));
+	return TCL_OK;
+    }
     result = Tcl_DictObjGet(interp, dictPtr, objv[objc-1], &valuePtr);
     if (result != TCL_OK) {
 	return result;
Index: tests/dict.test
===================================================================
RCS file: /cvsroot/tcl/tcl/tests/dict.test,v
retrieving revision 1.5
diff -u -r1.5 dict.test
--- tests/dict.test	14 Jan 2004 09:34:33 -0000	1.5
+++ tests/dict.test	14 Jan 2004 21:55:44 -0000
@@ -204,9 +204,7 @@
 test dict-9.2 {dict exists command} {dict exists {a b} b} 0
 test dict-9.3 {dict exists command} {dict exists {a {b c}} a b} 1
 test dict-9.4 {dict exists command} {dict exists {a {b c}} a c} 0
-test dict-9.5 {dict exists command} {
-    list [catch {dict exists {a {b c}} b c} msg] $msg
-} {1 {key "b" not known in dictionary}}
+test dict-9.5 {dict exists command} {dict exists {a {b c}} b c} 0
 test dict-9.6 {dict exists command} {
     list [catch {dict exists {a {b c d}} a c} msg] $msg
 } {1 {missing value to go with key}}
