source: doc/proposals/exceptions-pab.md@ 1955fac

Last change on this file since 1955fac was 30901eb6, checked in by Peter A. Buhr <pabuhr@…>, 7 days ago

updated exception proposal from PAB

  • Property mode set to 100644
File size: 10.9 KB
Line 
1# Exception Handling Changes
2
3This proposal changes the C&forall; exception handling mechanism from matching an object type to a function type.
4Using functions fits with function overloading in the C&forall; type system.
5
6The following shows a resumption example.
7
8```
9exception int resumpt( int, double ); // return result, no stack unwinding
10try {
11 i = resumpt( 3, 3.5 ); // exception raise
12} catch( int resumpt( int i, double d ) { // i = 3, d = 3.5
13 ... return 42; // return value to raise point
14}
15```
16
17The following shows a termination example.
18
19```
20exception void termin( double ); // void return, stack unwinding
21try {
22 termin( 3.5 ); // exception raise
23} catch( void termin( int i, double d ) { // i = 3, d = 3.5
24 ... // fall through to lexical block
25}
26```
27
28There is no explicit *raise* statement.
29(See Mesa Language Manual page 135, footnote)
30The compiler knows a function is an exception, so it does a search call (propagation) instead of a branch call.
31
32An exception function is denoted by the `exception` keyword to differentiate it from a regular function.
33An exception function is defined in a `catch` clause, which is a local, inline implementation of the exception function.
34The catch clause must specify the exception-function name and type for matching, and arbitrary parameter names to access the raise arguments.
35Data is carried from the raise call to the `catch` body via the argument/parameter mechanism, and data can be returned to the raise call via function return.
36An exception function cannot be passed as a function pointer nor can a raise use a pointer to an exception function.
37
38Overloading of the exception raise occurs among existing exception functions.
39
40```
41exception int fix( int, double );
42exception double fix( int, double );
43exception double fix( double );
44try {
45 int i = fix ( 3, 3.5 ); // choose best overloading
46} catch( int fix( int i, double d ) ) { // match with exact catch body
47} catch( double fix( int i, double d ) ) {
48} catch( double fix( double d ) ) {
49}
50```
51
52Normal overload resolution occur at the raise point, where the best fit *exception* function is chosen based on arguments and left-hand side.
53Matching between raise and catch is exact, as for function pointers, with the selected overload chosen at the raise point (compare mangled names dynamically)
54
55An exception function has a *kind*: return, noreturn, or dual, denoting resumption, termination, or both.
56An exception function cannot be overloaded on the exception kind.
57A return exception function must return a type, a noreturn exception function must return `void`, a dual exception function must return a type.
58A dual exception function is specified using the quasi-keyword `dual`.
59
60```
61exception int dual( int, double ) dual; // Dual, return result or exit handler block,
62 // no initial stack unwinding
63```
64
65Within the handler for kind return (stack not unwound), the handler *must* return a value as for any value returning function;
66any other control-flow statement or fallthrough is an error, like `break` or fallthrough on a value returning function.
67
68```
69 } catch( int fix( int i, float j ) ) { // stack not unwound, i/j accessible, => must return
70 return 42; // => return to fix call, not return of lexical function
71 break; // syntax error => must return
72 // fallthrough => must return
73 }
74```
75
76Within the handler for kind noreturn (stack unwound), `break` or fallthrough &rArr; exit handler, like in the `case` clause of a switch statement;
77any other control-flow statement works on the lexical context outside the handler.
78
79```
80 } catch( void recover( int i ) ) { // stack unwound, cannot return
81 break; // => exit catch routine
82 return; // => return from lexical function
83 // fallthrough => => break
84 }
85```
86
87Within the handler for kind dual (stack not unwound), `break` or fallthrough &rArr; exit handler and unwind stack, or return a value to the raise at top of the stack.
88
89```
90 } catch( int dual( int i, float j ) ) { // stack not unwound, can return
91 return 42; // => return to fix call, not return of nested function
92 break; // => exit catch body
93 // fallthrough => break
94 }
95```
96
97In all cases, a handler can raise another exception.
98
99Resumption example:
100
101```
102exception int fix( int, float );
103void foo() {
104 try {
105 for () {
106 ... if ( ... ) x = fix( 3, 5.4 ); ...
107 }
108 } catch( int fix( int i, float f ) ) {
109 ... return 42; // fix up problem and return to raise
110 }
111}
112```
113
114Termination example:
115
116```
117exception void end_of_file( ifstream is );
118void foo() {
119 int i;
120 try {
121 for () {
122 sin | i; // internally, calls end_of_file( is ) to raise exception
123 sout | i;
124 }
125 } catch( end_of_file( ifstream is ) && is == sin ) { // "is" used in predicate selection
126 // close file
127 }
128}
129```
130
131Dual example:
132
133```
134exception int dual( int i, float ) dual;
135void baz() {
136 try {
137 for () {
138 if ( ... ) x = dual( 3, 5.4 );
139 }
140 } catch( int dual( int i, float f ) ) { // stack not unwound
141 if ( ... ) return 42; // return to raise calls
142 if ( ... ) break; // exit catch body
143 // fallthrough => break
144 }
145}
146```
147
148
149## Default Handler
150
151An exception function can have a static body, which defines the action if the exception is not caught.
152If no body is specified, there are default actions added.
153This matches with `defaultResume` and `defaultTerminate` in current C&forall;/&mu;C++.
154Here a parameter name is necessary to access the raise argument(s).
155
156Resumption default:
157
158```
159exception int resumpt( int i ) { // called if no handler found
160 // return default correction or abort or raise another exception
161 // default if not specified is to call UnhandledException at resumer or joiner.
162}
163```
164
165Termination default:
166
167```
168exception void termin( double d ) { // called if no handler found
169 // abort or raise another exception
170 // default if not specified is to call UnhandledException at resumer or joiner.
171}
172```
173
174Dual default is the same as termination default, as having a default correction action is unlikely.
175
176
177## Polymorphism
178
179A catch clause can be polymorphic using the RTTI information in a mangled name.
180
181```
182forall( T | { T ?+?( T, T ); } )
183exception T foo( T t ) { return t + t; }
184
185 try (
186 i = foo( 3 ); // raise
187 } catch( int foo( int t ) ) { // monomorphic handler
188 return t + 3 / t; // can do something
189 } catch( forall( T | { T ?+?( T, T ) } ) T foo( T t ) ) { // polymorphic handler
190 return t + t; // cannot do much
191 }
192
193```
194
195Matching is a pattern match on the mangled name (assuming there is enough information):
196
197```
198_X3fooQ1_0_0_5__X16_operator_assignFBD0_BD0BD0__X12_constructorFv_BD0__X12_constructorFv_BD0BD0
199 __X11_destructorFv_BD0__X13_operator_addFBD0_BD0BD0__FBD0_BD0__1
200```
201
202
203## Separate Compilation
204
205**test.hfa**
206
207```
208typedef struct S {
209 void (*f)( int ); // general function pointer
210} S;
211
212void foo( int ) throws( E ); // external
213void bar( int ) throws( F );
214
215void mary( S & s ); // external
216void jane( S s );
217```
218
219**test2.cfa**
220
221```
222#include "test1.hfa"
223#include "stdlib.h"
224
225void foo( int ) { printf( "foo\n" ); }
226void bar( int ) { printf( "bar\n" ); }
227
228void mary( S & s ) {
229 s.f = random() % 2 ? foo : bar; // randomly insert a function
230}
231
232void jane( S s ) {
233 s.f( 3 ); // call field f
234}
235```
236
237**test1.cfa**
238
239```
240#include "test1.hfa"
241
242int main() {
243 S sfoo;
244
245 mary( sfoo ); // initialize field f
246
247 try {
248 jane( sfoo ); // calls sfoo.f in jane
249 } catch( ... ) {} // cannot statically check correct handler is present
250}
251```
252
253
254## `throws` Clause
255
256A `throws` clause can be added to a regular function to indicate alternate outcomes.
257
258```
259int foo(...) throws( int ex( int ), float ex( int ), char ex( double, double ) );
260```
261
262The `throws` clause is *not* part of the function type, and hence, is *not* used for overloading.
263Functions with a `throws` clause are handled by a separate type-checking pass, which examines the static call-structure to determine if calls are nested directly or indirectly within guarded blocks with matching catch clauses, e.g.:
264
265```
266int bar( int i ) throws( int fixup( int ) ); // might call fixup directly or indirectly
267
268void foo(...) {
269 ... i = bar( 3 ); ... // call statically nested within handler for fixup in baz
270}
271void baz() {
272 try {
273 foo(...);
274 } catch( int fixup( int ) ) { // handler for bar
275 ... return 42;
276 }
277}
278```
279
280If this type check fails because of separate compilation, e.g., `baz` is in a different translation unit, a warning is given, and a dynamic check is wrapped around the call to `bar` to verify only the specified exception functions are raised.
281This dual approach allows all forms of reuse to exist, and is similar to checked/unchecked exceptions in Java.
282
283
284## Polymorphism Extension
285
286A new kind of polymorphic parameter is introduced, `throws`, listing the possible empty set of exceptions (alternate outcomes) that can be raised exception, but a possibly empty set of exceptions.
287A set of exceptions can be used in an exception signature in the same way a single exception can be and are traced from callee to caller in the same way a single exception is.
288They do not interact with throws or catches as those work on concrete types.
289At the top and bottom of the polymorphic call stack concrete functions throw and catch the exception, passing through the polymorphic section.
290
291```
292forall( T, [N], throws E )
293 void apply( void op(T &) throws(E), array(T, N) & data ) throws(E) {
294 for ( i; N ) op( data[i] );
295}
296```
297Given the concrete usage:
298
299```
300void op( int ) throws( void WWW( int ), Y, Z ) { ... }
301array( int, 10 ) arr;
302try {
303 apply( op, arr ); // op(... ) => if (... ) WWW(...)
304} catch( void WWW( int ) ) { ... // apply can raise from op
305} catch( exception Y ) { ... // apply can raise from op
306} catch( exception Z ) { ... // apply can raise from op
307}
308```
309
310The call to `apply` composes a list of all potential exceptions thrown by arguments.
311
312Imagine we passed around a set description (a list of type identities for each exception in the set) and at the edges of the polymorphic space we check for an unknown exception type against these sets.
313The static checks should ensure that every exception falls into one set;
314in fact if there is only one knows set we know it must belong to it.
315Then we wrap it up with a little marker saying which set it is part of and which one in the set it is.
316That gets passed through as an opaque exception until we return to monomorphic code, then it is unpacked and passed normally.
317
318
319## More Examples
320
321```
322enum FloatExceptions ! { Invalid, ZeroDiv, Overflow, Underflow, Inexact };
323exception double arithmetic( double op1, double op2, int retcode ) {
324 // default abort
325}
326enum IntExceptions ! { ZeroDiv, Overflow, Underflow };
327exception double arithmetic( int op1, int op2, int retcode ) {
328 // default abort
329}
330
331void foo() {
332 double x = 3.5;
333 try {
334 x = x / 0.0;
335 } catch( double arithmetic( double op1, double op2, int retcode ) ) {
336 if ( retcode == FloatExceptions.ZeroDiv ) ... // analyze retcode
337 }
338
339 int i = 3;
340 try {
341 i = i / 0;
342 } catch( double arithmetic( int op1, int op2, int retcode ) ) {
343 if ( retcode == IntExceptions.ZeroDiv ) ... // analyze retcode
344 }
345}
346```
Note: See TracBrowser for help on using the repository browser.