| 1 | # Exception Handling Changes
|
|---|
| 2 |
|
|---|
| 3 | This proposal changes the C∀ exception handling mechanism from matching an object type to a function type.
|
|---|
| 4 | Using functions fits with function overloading in the C∀ type system.
|
|---|
| 5 |
|
|---|
| 6 | The following shows a resumption example.
|
|---|
| 7 |
|
|---|
| 8 | ```
|
|---|
| 9 | exception int resumpt( int, double ); // return result, no stack unwinding
|
|---|
| 10 | try {
|
|---|
| 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 |
|
|---|
| 17 | The following shows a termination example.
|
|---|
| 18 |
|
|---|
| 19 | ```
|
|---|
| 20 | exception void termin( double ); // void return, stack unwinding
|
|---|
| 21 | try {
|
|---|
| 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 |
|
|---|
| 28 | There is no explicit *raise* statement.
|
|---|
| 29 | (See Mesa Language Manual page 135, footnote)
|
|---|
| 30 | The compiler knows a function is an exception, so it does a search call (propagation) instead of a branch call.
|
|---|
| 31 |
|
|---|
| 32 | An exception function is denoted by the `exception` keyword to differentiate it from a regular function.
|
|---|
| 33 | An exception function is defined in a `catch` clause, which is a local, inline implementation of the exception function.
|
|---|
| 34 | The catch clause must specify the exception-function name and type for matching, and arbitrary parameter names to access the raise arguments.
|
|---|
| 35 | Data 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.
|
|---|
| 36 | An exception function cannot be passed as a function pointer nor can a raise use a pointer to an exception function.
|
|---|
| 37 |
|
|---|
| 38 | Overloading of the exception raise occurs among existing exception functions.
|
|---|
| 39 |
|
|---|
| 40 | ```
|
|---|
| 41 | exception int fix( int, double );
|
|---|
| 42 | exception double fix( int, double );
|
|---|
| 43 | exception double fix( double );
|
|---|
| 44 | try {
|
|---|
| 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 |
|
|---|
| 52 | Normal overload resolution occur at the raise point, where the best fit *exception* function is chosen based on arguments and left-hand side.
|
|---|
| 53 | Matching between raise and catch is exact, as for function pointers, with the selected overload chosen at the raise point (compare mangled names dynamically)
|
|---|
| 54 |
|
|---|
| 55 | An exception function has a *kind*: return, noreturn, or dual, denoting resumption, termination, or both.
|
|---|
| 56 | An exception function cannot be overloaded on the exception kind.
|
|---|
| 57 | A return exception function must return a type, a noreturn exception function must return `void`, a dual exception function must return a type.
|
|---|
| 58 | A dual exception function is specified using the quasi-keyword `dual`.
|
|---|
| 59 |
|
|---|
| 60 | ```
|
|---|
| 61 | exception int dual( int, double ) dual; // Dual, return result or exit handler block,
|
|---|
| 62 | // no initial stack unwinding
|
|---|
| 63 | ```
|
|---|
| 64 |
|
|---|
| 65 | Within the handler for kind return (stack not unwound), the handler *must* return a value as for any value returning function;
|
|---|
| 66 | any 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 |
|
|---|
| 76 | Within the handler for kind noreturn (stack unwound), `break` or fallthrough ⇒ exit handler, like in the `case` clause of a switch statement;
|
|---|
| 77 | any 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 |
|
|---|
| 87 | Within the handler for kind dual (stack not unwound), `break` or fallthrough ⇒ 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 |
|
|---|
| 97 | In all cases, a handler can raise another exception.
|
|---|
| 98 |
|
|---|
| 99 | Resumption example:
|
|---|
| 100 |
|
|---|
| 101 | ```
|
|---|
| 102 | exception int fix( int, float );
|
|---|
| 103 | void 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 |
|
|---|
| 114 | Termination example:
|
|---|
| 115 |
|
|---|
| 116 | ```
|
|---|
| 117 | exception void end_of_file( ifstream is );
|
|---|
| 118 | void 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 |
|
|---|
| 131 | Dual example:
|
|---|
| 132 |
|
|---|
| 133 | ```
|
|---|
| 134 | exception int dual( int i, float ) dual;
|
|---|
| 135 | void 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 |
|
|---|
| 151 | An exception function can have a static body, which defines the action if the exception is not caught.
|
|---|
| 152 | If no body is specified, there are default actions added.
|
|---|
| 153 | This matches with `defaultResume` and `defaultTerminate` in current C∀/μC++.
|
|---|
| 154 | Here a parameter name is necessary to access the raise argument(s).
|
|---|
| 155 |
|
|---|
| 156 | Resumption default:
|
|---|
| 157 |
|
|---|
| 158 | ```
|
|---|
| 159 | exception 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 |
|
|---|
| 165 | Termination default:
|
|---|
| 166 |
|
|---|
| 167 | ```
|
|---|
| 168 | exception 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 |
|
|---|
| 174 | Dual default is the same as termination default, as having a default correction action is unlikely.
|
|---|
| 175 |
|
|---|
| 176 |
|
|---|
| 177 | ## Polymorphism
|
|---|
| 178 |
|
|---|
| 179 | A catch clause can be polymorphic using the RTTI information in a mangled name.
|
|---|
| 180 |
|
|---|
| 181 | ```
|
|---|
| 182 | forall( T | { T ?+?( T, T ); } )
|
|---|
| 183 | exception 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 |
|
|---|
| 195 | Matching 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 | ```
|
|---|
| 208 | typedef struct S {
|
|---|
| 209 | void (*f)( int ); // general function pointer
|
|---|
| 210 | } S;
|
|---|
| 211 |
|
|---|
| 212 | void foo( int ) throws( E ); // external
|
|---|
| 213 | void bar( int ) throws( F );
|
|---|
| 214 |
|
|---|
| 215 | void mary( S & s ); // external
|
|---|
| 216 | void jane( S s );
|
|---|
| 217 | ```
|
|---|
| 218 |
|
|---|
| 219 | **test2.cfa**
|
|---|
| 220 |
|
|---|
| 221 | ```
|
|---|
| 222 | #include "test1.hfa"
|
|---|
| 223 | #include "stdlib.h"
|
|---|
| 224 |
|
|---|
| 225 | void foo( int ) { printf( "foo\n" ); }
|
|---|
| 226 | void bar( int ) { printf( "bar\n" ); }
|
|---|
| 227 |
|
|---|
| 228 | void mary( S & s ) {
|
|---|
| 229 | s.f = random() % 2 ? foo : bar; // randomly insert a function
|
|---|
| 230 | }
|
|---|
| 231 |
|
|---|
| 232 | void jane( S s ) {
|
|---|
| 233 | s.f( 3 ); // call field f
|
|---|
| 234 | }
|
|---|
| 235 | ```
|
|---|
| 236 |
|
|---|
| 237 | **test1.cfa**
|
|---|
| 238 |
|
|---|
| 239 | ```
|
|---|
| 240 | #include "test1.hfa"
|
|---|
| 241 |
|
|---|
| 242 | int 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 |
|
|---|
| 256 | A `throws` clause can be added to a regular function to indicate alternate outcomes.
|
|---|
| 257 |
|
|---|
| 258 | ```
|
|---|
| 259 | int foo(...) throws( int ex( int ), float ex( int ), char ex( double, double ) );
|
|---|
| 260 | ```
|
|---|
| 261 |
|
|---|
| 262 | The `throws` clause is *not* part of the function type, and hence, is *not* used for overloading.
|
|---|
| 263 | Functions 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 | ```
|
|---|
| 266 | int bar( int i ) throws( int fixup( int ) ); // might call fixup directly or indirectly
|
|---|
| 267 |
|
|---|
| 268 | void foo(...) {
|
|---|
| 269 | ... i = bar( 3 ); ... // call statically nested within handler for fixup in baz
|
|---|
| 270 | }
|
|---|
| 271 | void baz() {
|
|---|
| 272 | try {
|
|---|
| 273 | foo(...);
|
|---|
| 274 | } catch( int fixup( int ) ) { // handler for bar
|
|---|
| 275 | ... return 42;
|
|---|
| 276 | }
|
|---|
| 277 | }
|
|---|
| 278 | ```
|
|---|
| 279 |
|
|---|
| 280 | If 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.
|
|---|
| 281 | This dual approach allows all forms of reuse to exist, and is similar to checked/unchecked exceptions in Java.
|
|---|
| 282 |
|
|---|
| 283 |
|
|---|
| 284 | ## Polymorphism Extension
|
|---|
| 285 |
|
|---|
| 286 | A 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.
|
|---|
| 287 | A 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.
|
|---|
| 288 | They do not interact with throws or catches as those work on concrete types.
|
|---|
| 289 | At the top and bottom of the polymorphic call stack concrete functions throw and catch the exception, passing through the polymorphic section.
|
|---|
| 290 |
|
|---|
| 291 | ```
|
|---|
| 292 | forall( 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 | ```
|
|---|
| 297 | Given the concrete usage:
|
|---|
| 298 |
|
|---|
| 299 | ```
|
|---|
| 300 | void op( int ) throws( void WWW( int ), Y, Z ) { ... }
|
|---|
| 301 | array( int, 10 ) arr;
|
|---|
| 302 | try {
|
|---|
| 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 |
|
|---|
| 310 | The call to `apply` composes a list of all potential exceptions thrown by arguments.
|
|---|
| 311 |
|
|---|
| 312 | Imagine 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.
|
|---|
| 313 | The static checks should ensure that every exception falls into one set;
|
|---|
| 314 | in fact if there is only one knows set we know it must belong to it.
|
|---|
| 315 | Then we wrap it up with a little marker saying which set it is part of and which one in the set it is.
|
|---|
| 316 | That 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 | ```
|
|---|
| 322 | enum FloatExceptions ! { Invalid, ZeroDiv, Overflow, Underflow, Inexact };
|
|---|
| 323 | exception double arithmetic( double op1, double op2, int retcode ) {
|
|---|
| 324 | // default abort
|
|---|
| 325 | }
|
|---|
| 326 | enum IntExceptions ! { ZeroDiv, Overflow, Underflow };
|
|---|
| 327 | exception double arithmetic( int op1, int op2, int retcode ) {
|
|---|
| 328 | // default abort
|
|---|
| 329 | }
|
|---|
| 330 |
|
|---|
| 331 | void 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 | ```
|
|---|