Subject: [33b7abb8a2] [7174354ecb] Rewrite TclCompileThrowCmd().

From: dgp <[email-masked]>
Date: Tue, 11 Jun 2013 16:44:34 +0000

---
 generic/tclCompCmdsSZ.c | 105 +++++++++++++++++++++---------------------------
 tests/error.test        |   6 +++
 2 files changed, 52 insertions(+), 59 deletions(-)

diff --git a/generic/tclCompCmdsSZ.c b/generic/tclCompCmdsSZ.c
index 5d67166..b9ee1d4 100644
--- a/generic/tclCompCmdsSZ.c
+++ b/generic/tclCompCmdsSZ.c
@@ -1942,9 +1942,9 @@ TclCompileThrowCmd(
 {
     DefineLineInformation;	/* TIP #280 */
     int numWords = parsePtr->numWords;
-    int savedStackDepth = envPtr->currStackDepth;
     Tcl_Token *codeToken, *msgToken;
     Tcl_Obj *objPtr;
+    int codeKnown, codeIsList, codeIsValid, len;
 
     if (numWords != 3) {
 	return TCL_ERROR;
@@ -1954,77 +1954,64 @@ TclCompileThrowCmd(
 
     TclNewObj(objPtr);
     Tcl_IncrRefCount(objPtr);
-    if (TclWordKnownAtCompileTime(codeToken, objPtr)) {
-	Tcl_Obj *errPtr, *dictPtr;
-	const char *string;
-	int len;
 
-	/*
-	 * The code is known at compilation time. This allows us to issue a
-	 * very efficient sequence of instructions.
-	 */
+    codeKnown = TclWordKnownAtCompileTime(codeToken, objPtr);
 
-	if (Tcl_ListObjLength(interp, objPtr, &len) != TCL_OK) {
-	    /*
-	     * Must still do this; might generate an error when getting this
-	     * "ignored" value prepared as an argument.
-	     */
+    /*
+     * First we must emit the code to substitute the arguments.  This
+     * must come first in case substitution raises errors.
+     */
+    if (!codeKnown) {
+	CompileWord(envPtr, codeToken, interp, 1);
+	PUSH(			"-errorcode");
+    }
+    CompileWord(envPtr, msgToken, interp, 2);
 
-	    CompileWord(envPtr, msgToken, interp, 2);
-	    TclCompileSyntaxError(interp, envPtr);
-	    Tcl_DecrRefCount(objPtr);
-	    envPtr->currStackDepth = savedStackDepth + 1;
-	    return TCL_OK;
-	}
-	if (len == 0) {
-	    /*
-	     * Must still do this; might generate an error when getting this
-	     * "ignored" value prepared as an argument.
-	     */
+    codeIsList = codeKnown && (TCL_OK == 
+	    Tcl_ListObjLength(interp, objPtr, &len));
+    codeIsValid = codeIsList && (len != 0);
+
+    if (codeIsValid) {
+	Tcl_Obj *errPtr, *dictPtr;
 
-	    CompileWord(envPtr, msgToken, interp, 2);
-	    goto issueErrorForEmptyCode;
-	}
 	TclNewLiteralStringObj(errPtr, "-errorcode");
 	TclNewObj(dictPtr);
 	Tcl_DictObjPut(NULL, dictPtr, errPtr, objPtr);
-	Tcl_IncrRefCount(dictPtr);
-	string = Tcl_GetStringFromObj(dictPtr, &len);
-	CompileWord(envPtr, msgToken, interp, 2);
-	PushLiteral(envPtr, string, len);
-	TclDecrRefCount(dictPtr);
-	OP44(				RETURN_IMM, 1, 0);
-	envPtr->currStackDepth = savedStackDepth + 1;
-    } else {
-	/*
-	 * When the code token is not known at compilation time, we need to do
-	 * a little bit more work. The main tricky bit here is that the error
-	 * code has to be a list (a [throw] restriction) so we must emit extra
-	 * instructions to enforce that condition.
-	 */
+	TclEmitPush(TclAddLiteralObj(envPtr, dictPtr, NULL), envPtr);
+    }
+    TclDecrRefCount(objPtr);
 
-	CompileWord(envPtr, codeToken, interp, 1);
-	PUSH(				"-errorcode");
-	CompileWord(envPtr, msgToken, interp, 2);
-	OP4(				REVERSE, 3);
-	OP(				DUP);
-	OP(				LIST_LENGTH);
-	OP1(				JUMP_FALSE1, 16);
-	OP4(				LIST, 2);
-	OP44(				RETURN_IMM, 1, 0);
+    /*
+     * Simpler bytecodes when we detect invalid arguments at compile time.
+     */
+    if (codeKnown && !codeIsValid) {
+	OP(			POP);
+	if (codeIsList) {
+	    /* Must be an empty list */
+	    goto issueErrorForEmptyCode;
+	}
+	TclCompileSyntaxError(interp, envPtr);
+	return TCL_OK;
+    }
 
+    if (!codeKnown) {
 	/*
-	 * Generate an error for being an empty list. Can't leverage anything
-	 * else to do this for us.
+	 * Argument validity checking has to be done by bytecode at
+	 * run time.
 	 */
-
+	OP4(			REVERSE, 3);
+	OP(			DUP);
+	OP(			LIST_LENGTH);
+	OP1(			JUMP_FALSE1, 16);
+	OP4(			LIST, 2);
+	OP44(			RETURN_IMM, 1, 0);
+	OP(			POP);
+	OP(			POP);
     issueErrorForEmptyCode:
-	PUSH(				"type must be non-empty list");
-	PUSH(				"");
-	OP44(				RETURN_IMM, 1, 0);
+	PUSH(			"type must be non-empty list");
+	PUSH(			"-errorcode {TCL OPERATION THROW BADEXCEPTION}");
     }
-    envPtr->currStackDepth = savedStackDepth + 1;
-    TclDecrRefCount(objPtr);
+    OP44(			RETURN_IMM, 1, 0);
     return TCL_OK;
 }
 
diff --git a/tests/error.test b/tests/error.test
index 273577a..06f8eca 100644
--- a/tests/error.test
+++ b/tests/error.test
@@ -317,6 +317,12 @@ test error-8.8 {throw syntax checks} -returnCodes error -body {
 test error-8.9 {throw syntax checks} -returnCodes error -body {
     throw {} foo
 } -result {type must be non-empty list}
+test error-8.10 {Bug 33b7abb8a2: throw stack usage} -returnCodes error -body {
+    apply {code {throw $code foo}} {}
+} -result {type must be non-empty list}
+test error-8.11 {Bug 7174354ecb: throw error message} -returnCodes error  -body {
+    throw {not {}a list} x[]y
+} -result {list element in braces followed by "a" instead of space}
 
 # simple try tests: body completes with code ok
 
-- 
cgit v0.12