This is not necessarily the current version of this TIP.
| TIP: | 87 |
| Title: | Allow Tcl Access to the Recursion Limit |
| Version: | $Revision: 1.6 $ |
| Authors: |
Stephen Trier <sct at po dot cwru dot edu> Richard Suchenwirth <richard dot suchenwirth-bauersachs at siemens dot com> |
| State: | Draft |
| Type: | Project |
| Tcl-Version: | 8.4 |
| Vote: | Pending |
| Created: | Tuesday, 19 February 2002 |
| Discussions To: | news:comp.lang.tcl |
| Keywords: | Tcl_SetRecusionLimit, recursion limit |
An extension to the [interp] command, [interp recursionlimit], will permit Tcl scripts to control their own recursion limits. Until now, this limit has been changeable from a C API, but not from within Tcl.
As of Tcl 8.4a3, Tcl scripts must live with the default recursion depth of 1000 nested calls to the Tcl_Eval family of functions or resort to C code to change the limit. Nevertheless, Tcl programmers may find it useful to reduce the limit when debugging or to increase it for scripts that include deeply recursive functions. The changes proposed in this TIP will make this possible in pure Tcl code.
Add subcommands to [interp] and to the slave interpreter object command with the following syntax:
interp recursionlimit path ?newlimit?
slave recursionlimit ?newlimit?
The parameter newlimit must be a positive integer. When it is present, the limit is changed to newlimit and the command returns the new recursion limit. If the newlimit parameter is absent, the command returns the current recursion limit.
No maximum value is enforced. It is the programmer's responsibility to ensure the recursion limit will not overflow the process stack.
A safe interpreter is not allowed to change the recursion limit for itself nor for any other interpreter. Attempting to do so will generate an error. Safe interpreters are allowed to query recursion limits.
If the Tcl_Eval nesting level is deeper than the new recursion limit, the new limit will be set, but an error will be issued reading "falling back due to new recursion limit".
Remove the now-unnecessary testsetrecursionlimit command.
Add documentation for the new subcommands, including a warning about stack overflow, much like the warning in the documentation for Tcl_SetRecursionLimit().
Add tests for the new subcommands.
Discussion of this TIP took place in the following threads:
http://groups.google.com/groups?hl=en&threadm=3C6D0A88.5DC9D8B4%40utdt.edu
http://groups.google.com/groups?hl=en&threadm=3C73E98A.8ED9DDE6%40cisco.com
Using a command or variable ::tcl::recursionLimit to manipulate the limit was initially considered, but Miguel Sofer suggested making the function a subcommand of [interp] because the recursion limit is logically an attribute of each interpreter. Miguel also pointed out that implementing TclpCheckStackSpace() for Unix would mitigate the dangers of setting the recursion limit too high.
comp.lang.tcl saw some discussion of whether it would be appropriate to have a way to completely remove the recursion limit. The consensus was to not add such a feature.
The initial version of this TIP did not provide for a diagnostic error message for the case where the nesting is already deeper than the new recursion level. Ken Fitch, Don Porter, Miguel Sofer, and Donal Fellows discussed whether this was important. This version of the TIP uses Donal Fellows's suggestion of changing the recursion limit as requested, but providing a meaningful error message if the nesting is too deep for the new limit.
Donal Fellows suggested that slave interpreters should inherit their recursion limit from their parent. As it turns out, this behavior was already present but was not documented. The reference implementation documents it.
A full implementation of this TIP, with tests and documentation, is patch number 522849 on SourceForge. The following is the key portion of the implementation.
*** generic/tclInterp.c.virgin Fri Feb 22 22:04:54 2002
--- generic/tclInterp.c Tue Feb 26 00:09:08 2002
***************
*** 351,364 ****
"alias", "aliases", "create", "delete",
"eval", "exists", "expose", "hide",
"hidden", "issafe", "invokehidden", "marktrusted",
! "slaves", "share", "target", "transfer",
NULL
};
enum option {
OPT_ALIAS, OPT_ALIASES, OPT_CREATE, OPT_DELETE,
OPT_EVAL, OPT_EXISTS, OPT_EXPOSE, OPT_HIDE,
OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHID, OPT_MARKTRUSTED,
! OPT_SLAVES, OPT_SHARE, OPT_TARGET, OPT_TRANSFER
};
--- 351,366 ----
"alias", "aliases", "create", "delete",
"eval", "exists", "expose", "hide",
"hidden", "issafe", "invokehidden", "marktrusted",
! "recursionlimit", "slaves", "share",
! "target", "transfer",
NULL
};
enum option {
OPT_ALIAS, OPT_ALIASES, OPT_CREATE, OPT_DELETE,
OPT_EVAL, OPT_EXISTS, OPT_EXPOSE, OPT_HIDE,
OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHID, OPT_MARKTRUSTED,
! OPT_RECLIMIT, OPT_SLAVES, OPT_SHARE,
! OPT_TARGET, OPT_TRANSFER
};
***************
*** 630,635 ****
--- 632,673 ----
}
return SlaveMarkTrusted(interp, slaveInterp);
}
+ case OPT_RECLIMIT: {
+ Tcl_Interp *slaveInterp;
+ int limit;
+
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "path ?newlimit?");
+ return TCL_ERROR;
+ }
+ slaveInterp = GetInterp(interp, objv[2]);
+ if (slaveInterp == NULL) {
+ return TCL_ERROR;
+ }
+ if (objc == 4) {
+ if (Tcl_GetIntFromObj(interp, objv[3],
+ &limit) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ if (limit <= 0) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("recursion limit must be > 0", -1));
+ return TCL_ERROR;
+ }
+ Tcl_SetRecursionLimit(slaveInterp, limit);
+ if (((Interp *)slaveInterp)->numLevels > limit) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "falling back due to new recursion limit", -1));
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, objv[3]);
+ return TCL_OK;
+ } else {
+ limit = Tcl_SetRecursionLimit(slaveInterp, 0);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(limit));
+ return TCL_OK;
+ }
+ }
case OPT_SLAVES: {
Tcl_Interp *slaveInterp;
InterpInfo *iiPtr;
***************
*** 1832,1843 ****
static CONST char *options[] = {
"alias", "aliases", "eval", "expose",
"hide", "hidden", "issafe", "invokehidden",
! "marktrusted", NULL
};
enum options {
OPT_ALIAS, OPT_ALIASES, OPT_EVAL, OPT_EXPOSE,
OPT_HIDE, OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHIDDEN,
! OPT_MARKTRUSTED
};
slaveInterp = (Tcl_Interp *) clientData;
--- 1870,1881 ----
static CONST char *options[] = {
"alias", "aliases", "eval", "expose",
"hide", "hidden", "issafe", "invokehidden",
! "marktrusted", "recursionlimit", NULL
};
enum options {
OPT_ALIAS, OPT_ALIASES, OPT_EVAL, OPT_EXPOSE,
OPT_HIDE, OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHIDDEN,
! OPT_MARKTRUSTED, OPT_RECLIMIT
};
slaveInterp = (Tcl_Interp *) clientData;
***************
*** 1954,1959 ****
--- 1992,2028 ----
return TCL_ERROR;
}
return SlaveMarkTrusted(interp, slaveInterp);
+ }
+ case OPT_RECLIMIT: {
+ int limit;
+
+ if (objc != 2 && objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?newlimit?");
+ return TCL_ERROR;
+ }
+ if (objc == 3) {
+ if (Tcl_GetIntFromObj(interp, objv[2],
+ &limit) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ if (limit <= 0) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("recursion limit must be > 0", -1));
+ return TCL_ERROR;
+ }
+ Tcl_SetRecursionLimit(slaveInterp, limit);
+ if (((Interp *)slaveInterp)->numLevels > limit) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "falling back due to new recursion limit", -1));
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, objv[2]);
+ return TCL_OK;
+ } else {
+ limit = Tcl_SetRecursionLimit(slaveInterp, 0);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(limit));
+ return TCL_OK;
+ }
}
}
This document is in the public domain.
This is not necessarily the current version of this TIP.