Check-in [0eac074208]

Login
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to tclconference@googlegroups.com
or submit via the online form by Sep 9.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Editorial cleanup of 290
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:0eac0742082eb8dde183ff90cd14ea2bf1936bb4e1a5c13dbd0bd245007a3829
User & Date: dkf 2018-10-31 09:46:38
Context
2018-11-01
08:35
Tidy up 507 check-in: c8ffbc0775 user: dkf tags: trunk
2018-10-31
09:46
Editorial cleanup of 290 check-in: 0eac074208 user: dkf tags: trunk
2018-10-30
07:43
TIP518: event on no widgets: updated tag/url of former tip 454 implementation check-in: 8c14b826e2 user: oehhar tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to tip/290.md.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
..
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
..
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94





95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# Abstract

This TIP proposes the possibility to register a custom command as error and exception handler.

# Rationale

Errors are thrown in the Tcl interpreter through the **error** command or
from a C extension that returns TCL\_ERROR. When an error is thrown, the global
_errorInfo_ variable is filled with rudimentary stacktrace information and
the error message itself. The global _errorCode_ variable can contain an
error code if this is provided by the command that has thrown the error.

Errors can be caught with the **catch** command. In this case, the
_errorInfo_ variable is still filled with the information mentioned above,
but the error is not presented to the interpreter. If the error is not caught,
................................................................................
In other languages such as LISP, this problem is addressed by
stopping the execution at the position where the error was thrown \(preserving the current callframe\) and presenting the developer with a console that
enables him to introspect the running program. Although Tcl has very good
introspection capabilities, it is not possible to use them in an error case,
because the execution just aborts and the stacktrace is unwound at once. For
errors generated with the **error** command, it is possible to overwrite
this command and provide more advanced functionality, but this is not
possible if errors are generated in C code by _return TCL\_ERROR_.

The proposed implementation addresses this problem by a custom error command
that is executed whenever an error occurs in the execution of Tcl code. This
opens a range of implementation possibilities for error handling, for
instance:

    * registering a _breakpoint_ command that stops execution at the error
      position and opens a console for introspection

    * registering a more advanced \(Tk\) debugger that opens on error for
      introspection.

    * registering a command that captures the state of each call-frame up to
      the one where the error was thrown and writes that state to a file. This
      file can later be debugged \(with an appropriate tool\).

# Alternatives

 1. Overwriting of the **error** command
As stated abovem, this works only for errors generated from Tcl code, whereas it does not work for errors from C extensions.

 2. Leavestep execution traces
In Tcl >=8.4 it is possible to register execution traces on _leavestep_ to commands. To implement part of the proposed functionality it would be possible to add a leavestep trace procedure, which - after each command - checks for the return code and acts accordingly. The problem with this approach is, that the procedure is called after _each_ command, independent from whether it returned an error or not. This can slow down the program execution more or less significantly. Furthermore, it is not easy if at all possible to capture occurences of **catch** by this way, so it is complicated or impossible to react on caught errors.

It might be possible to get part or most of the proposed functionality by _trace add execution leavestep_ or overwriting of **error**. But it is the responsibility of the core language \(even more when it is a dynamic language\) to provide customizable and advanced error handling. The TIP proposes in this direction.

# Specification

The implementation consists of two parts: a registration command for the custom command and a place where the handler is called. For this to work, there are some minor changes necessary to the Tcl execution engine and to the Interp structure. For running the handler on caught and or uncaught errors \(depending on how the user wants to have it\) it is necessary to capture the current level of "catch"es that occure during execution.

The registration command is responsible for:

................................................................................

   * change the execution details \(caught and/or uncaught errors\) and the command

   * unregister the command and thus get back to the current behaviour in error cases

Since the functionality is very similar to the family of **trace** commands, the proposed registration command is an extension to trace:

**trace set exception ?-caught? ?-uncaught? ?command?**: Registration and modification

The arguments _-caught_ and/or _-uncaught_ to _trace set exception_ modify the run conditions for the currently registered handler \(run on caught/uncaught errors\). With the _command_ argument, this is set as the new handler. The return code and result of the command that was executing is appended to the registered _command_. So, the real call is: _command code result_.

**trace info exception**: Information about the current handler

This command returns a list with the elements \{-caught y/n -uncaught y/n script\}, where caught and uncaught flags are specified and script is the currently registered handler.






**trace unset exception**: Remove registerred handler

Any previously set error handler is unregistered.

The _command_ that is registered will quell the error if it returns normal \(return code 0\). If the script returns abnormal, it's return code is returned to the interpreter. Errors inside the handler are not trapped by the script again, rather they are presented to the interpreter as usual - otherwise this would result in an endless loop.

The changes in the execution engine should be done so that:

   * existing functionality is not disturbed

   * the call frame is preserved after the error occured - thus the custom command is run in the same level as where the error was thrown

   * the _::errorInfo_ and _::errorCode_ variables are updated to contain the error information **that is available in the current callframe**. This information must be updated before the custom command is run, so that it is accessible from there.

The innermost function that is called on Tcl code execution is _TclEvalObjvInternal\(\)_. It is called from others to execute a command and returns the code that the executed command returned. It's the best place to trigger the error handler execution, but whether errors are catched \(catchLevel\) must be present at this time. Therefore, this level is stored in the current Interp\* from within the callers of _TclEvalObjvInternal\(\)_. The catch level can be determined either from _TclExecuteByteCode\(\)_ or from _Tcl\_CatchObjCmd\(\)_ directly.

The errorInfo and errorCode variables are set directly before the handler is run. This ensures that they are updated properly. Eventually registerred traces on this variable are handled as usual, before the custom error command is executed.

# Reference Implementation

A reference implementation is available from sourceforge as a patch against Tcl 8.5a5 <http://sourceforge.net/support/tracker.php?aid=1587317> .

# Usage Example

Here is a sample procedure that can be used to stop execution on error \(return code 1\) and
introspect the program using stdin/stdout. Other return codes are returned together with the previous result. The procedure was mainly implemented by Neil Madden as **debug-repl** and is available \(with a short discussion on the topic\) on 
<http://lambda-the-ultimate.org/node/1544#comment-18446> :

	package provide debug 1.0
	
	proc up {} {
	    uplevel 2 {
	        breakpoint
	    }







|







 







|






|
|

|
|

|
|
|







|

|







 







|

|

|

|

>
>
>
>
>
|













|





|




|
<







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
..
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
..
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

126
127
128
129
130
131
132
# Abstract

This TIP proposes the possibility to register a custom command as error and exception handler.

# Rationale

Errors are thrown in the Tcl interpreter through the **error** command or
from a C extension that returns `TCL_ERROR`. When an error is thrown, the global
_errorInfo_ variable is filled with rudimentary stacktrace information and
the error message itself. The global _errorCode_ variable can contain an
error code if this is provided by the command that has thrown the error.

Errors can be caught with the **catch** command. In this case, the
_errorInfo_ variable is still filled with the information mentioned above,
but the error is not presented to the interpreter. If the error is not caught,
................................................................................
In other languages such as LISP, this problem is addressed by
stopping the execution at the position where the error was thrown \(preserving the current callframe\) and presenting the developer with a console that
enables him to introspect the running program. Although Tcl has very good
introspection capabilities, it is not possible to use them in an error case,
because the execution just aborts and the stacktrace is unwound at once. For
errors generated with the **error** command, it is possible to overwrite
this command and provide more advanced functionality, but this is not
possible if errors are generated in C code by `return TCL_ERROR`.

The proposed implementation addresses this problem by a custom error command
that is executed whenever an error occurs in the execution of Tcl code. This
opens a range of implementation possibilities for error handling, for
instance:

 * registering a _breakpoint_ command that stops execution at the error
   position and opens a console for introspection

 * registering a more advanced \(Tk\) debugger that opens on error for
   introspection.

 * registering a command that captures the state of each call-frame up to
   the one where the error was thrown and writes that state to a file. This
   file can later be debugged \(with an appropriate tool\).

# Alternatives

 1. Overwriting of the **error** command
As stated abovem, this works only for errors generated from Tcl code, whereas it does not work for errors from C extensions.

 2. Leavestep execution traces
In Tcl &ge; 8.4 it is possible to register execution traces on _leavestep_ to commands. To implement part of the proposed functionality it would be possible to add a leavestep trace procedure, which - after each command - checks for the return code and acts accordingly. The problem with this approach is, that the procedure is called after _each_ command, independent from whether it returned an error or not. This can slow down the program execution more or less significantly. Furthermore, it is not easy if at all possible to capture occurences of **catch** by this way, so it is complicated or impossible to react on caught errors.

It might be possible to get part or most of the proposed functionality by `trace add execution leavestep` or overwriting of **error**. But it is the responsibility of the core language \(even more when it is a dynamic language\) to provide customizable and advanced error handling. The TIP proposes in this direction.

# Specification

The implementation consists of two parts: a registration command for the custom command and a place where the handler is called. For this to work, there are some minor changes necessary to the Tcl execution engine and to the Interp structure. For running the handler on caught and or uncaught errors \(depending on how the user wants to have it\) it is necessary to capture the current level of "catch"es that occure during execution.

The registration command is responsible for:

................................................................................

   * change the execution details \(caught and/or uncaught errors\) and the command

   * unregister the command and thus get back to the current behaviour in error cases

Since the functionality is very similar to the family of **trace** commands, the proposed registration command is an extension to trace:

 > **trace set exception** ?**-caught**? ?**-uncaught**? ?_command_?: Registration and modification

The arguments **-caught** and/or **-uncaught** to _trace set exception_ modify the run conditions for the currently registered handler \(run on caught/uncaught errors\). With the _command_ argument, this is set as the new handler. The return code and result of the command that was executing is appended to the registered _command_. So, the real call is: _command code result_.

 > **trace info exception**: Information about the current handler

This command returns a list with the elements:

 > **-caught** _bool_ **-uncaught** _bool_ _script_

where both **-caught** and **-uncaught** are specified (and each is associated with
a boolean) and _script_ is the currently registered handler.

 > **trace unset exception**: Remove registered handler

Any previously set error handler is unregistered.

The _command_ that is registered will quell the error if it returns normal \(return code 0\). If the script returns abnormal, it's return code is returned to the interpreter. Errors inside the handler are not trapped by the script again, rather they are presented to the interpreter as usual - otherwise this would result in an endless loop.

The changes in the execution engine should be done so that:

   * existing functionality is not disturbed

   * the call frame is preserved after the error occured - thus the custom command is run in the same level as where the error was thrown

   * the _::errorInfo_ and _::errorCode_ variables are updated to contain the error information **that is available in the current callframe**. This information must be updated before the custom command is run, so that it is accessible from there.

The innermost function that is called on Tcl code execution is `TclEvalObjvInternal()`. It is called from others to execute a command and returns the code that the executed command returned. It's the best place to trigger the error handler execution, but whether errors are catched \(catchLevel\) must be present at this time. Therefore, this level is stored in the current Interp\* from within the callers of `TclEvalObjvInternal()`. The catch level can be determined either from `TclExecuteByteCode()` or from `Tcl_CatchObjCmd()` directly.

The errorInfo and errorCode variables are set directly before the handler is run. This ensures that they are updated properly. Eventually registerred traces on this variable are handled as usual, before the custom error command is executed.

# Reference Implementation

A reference implementation is available from sourceforge as a [patch against Tcl 8.5a5](http://sourceforge.net/support/tracker.php?aid=1587317).

# Usage Example

Here is a sample procedure that can be used to stop execution on error \(return code 1\) and
introspect the program using stdin/stdout. Other return codes are returned together with the previous result. The procedure was mainly implemented by Neil Madden as **debug-repl** and is [available](http://lambda-the-ultimate.org/node/1544#comment-18446) \(with a short discussion on the topic\):


	package provide debug 1.0
	
	proc up {} {
	    uplevel 2 {
	        breakpoint
	    }