First: note that I have limited experience of designing ways of handling errors, so this is not offered as a template for how to do the job: it is just here to serve the needs of the Toy. The following fragments I've ripped out of status.h (which used to be tot.h) because they were cluttering it up. Some are current (typically with more prominent headings): others degenerate into burble - Eddy/1998/Dec/28.
Update: time for me to do drastic revisions here - Eddy/1999/Dec/1.
ooh, look - five years and more untouched ! - Eddy/2005/March/29. Stray thoughts on waking up at the dead of night. Can of course implement some try{...}catch{...}-equivalent for dealing with signals (null deref, overflow, fpe, ...), using setjmp/longjmp. Worth having two compilation modes, controlled by a -D; in one, macros OOM_CHECK(p) and PTR_CHECK(p) are void and subsequent code doubtless null derefs, which signal() has been set up to catch; in the other, signal() doesn't catch null deref and these two macros longjmp suitable messages if (p == 0). Hmm ... sigsetjmp/siglongjmp sound fun ...
When a record is invoked it is passed the address of a status record. This status record is auto (i.e. on the stack) in the scope of whoever invoked the record. The invocation tells the invoker about problems by perturbing this status record. Success is communicated by not changing its .how; the invoker detects trouble by looking for any such change.
The archetypical status record comes into being as a block of code begins executing, is initialised with a .how supplied by its context (usually the up passed to an invocation which is exercising our block of code), has its address passed down to an invocation and is inspected, after this invocation, for any change in its .how. If this has changed, something has gone wrong; otherwise, the block which brought the status record into being carries on with whatever it `normally' does.
If something has gone wrong, either we were on the look-out for it (i.e. ready to catch it) or we're just going to give up on what we're doing and pass the buck to whoever invoked us. In this `hopeless' case, we need to have the option of contributing some `traceback' information to the buck as we pass it on. If we catch the change, we may want to have this traceback information at our disposal; we may also want to be told `what went wrong' originally - i.e. the exception thrown, along with any associated data. Possibly after inspecting these data, we may also wish to `re-raise' the exception, possibly contributing to it.
When not catching an exception, we may also wish to do some `tidy-up' that is appropriate following failure to do what we set out to do; we must then (possibly contributing traceback on the way) pass the buck, much as if we were re-raising the exception.
The other flavour of status record is the one used to raise an exception. This gets digested by the up.how of the context raising the exception; that records on up.what an object which records the details of the exception, as encoded on our status record, and builds in some infrastructure for recording traceback information.
When an invocation doesn't change the .how of the status record it was passed, it also hasn't placed the invoker under any obligation to tidy up the status record it used. When a catcher has caught, the caller who used it consults the catcher's spec to discover what, if any, tidy-up obligations it requires - but when it hasn't caught, it definitely contributes no such obligations.
A judge may do anything it likes to a status record with whose ->how it is suitably familiar, whether this status record be the one passed to the judge or one it finds by chasing ->up from it.
In code which uses judges and status, a function which is passed a status record as its means of communicating with its caller will usually presume that this is a `true' status record (i.e. no private fields), as are all status records upwards from it with the same ->how. To signal that it is unhappy, it can mess with this record and, at its option, with any that it (or some judge or tool it calls) recognises: but it shouldn't normally mess with the ->up of any record above the one it is given and it should only mess with the ->how or private fields of a record further up if it is familiar with that record's existing ->how.
Now, such code is going to hang its context status off the ->up of an auto (possibly via some intermediaries) and pass this down to a tool: on return it'll assume all's well unless the ->up of what it passed down has been frobbed. In the event of difficulty, it's going to call some judge (e.g. the ->how of its context) on its auto and see whether it gets the auto back: it then goes into `coping' (including scrutinising the status just judged for any messages it recognises from the judge) of some simple kind; but if it gets some other reply it gives up. So any frobbing of its context is to be done by the judge: who must therefore be allowed to frob both the status record it is given and its ->up (which needs to be a different record).
Describe a status record as `proper' if: it is an auto variable of some function on the stack; its ->up is an auto of either this function or the one to which control will pass when it returns; and both these status records lack `private' fields.
Describe a judge as normal if it expects its input to be proper and have normal judges as the ->how of the two auto status records just discussed. Of course, if it finds some judge it recognises as a relevant ->how, whether or not this matches its expectation, its expectations may change: but it will assume that it can delegate its job to an ->how it doesn't recognise on its input or the ->up from there. A judge, buck, whose input was `here' could do this like:
if (here->up->how == &buck)
return (here->how == &buck) ? here : (*(here->how))(here);
return (*(here->up->how))(here);
Note that the recognition of ->how or ->up->how as &buck in this illustration could be some more complex testing and response: the important thing is that the judge must handle the cases where the ->how to which it is to delegate is itself - otherwise, it'll just call itself until the stack overflows !
A normal judge may transcribe the ->how of its given record to ->up->how, in which case it will signal this (to ->up's owner) by frobbing ->up->up and (to its own caller) by returning unhappy. The usual way to frob ->up->up is to point it at ->up, but a judge may chose to point it to some static status chain instead: likewise, the usual unhappy return is ->up, but again a static chain will do (what matters is that it isn't the judge's input: and the judge may have some callers who know to watch for the relevant static), as will anything further ->up the chain than ->up.
Note that normal judges may delegate to abnormal ones when they know enough about them and their input. They then rely on the abnormal to do nothing which will abrogate the constraints on its own interface.
A judge called on a chain will generally be the ->how of some status record in the chain: some will check this, some will even reject any chain unless it starts in a record whose ->how is the judge. Others will only look at records whose ->how they are, judging them but accepting all others; yet others might check that they don't appear in the chain (e.g. a recursion blocker).
A judge may frob the status chain it sees in various ways: however, it only frobs a status record in so far as it is familiar enough with the record's judge to understand how that judge (and its peers) will interpret the frobbed record.
Some judges will return happy despite frobbing the upwards status chain. A process traversing some tree in search of matches to some key might, for instance, use this to add each match it finds to a chain hanging off a status record it recognises further up the stack. Likewise, an exception thrower would record the details of its exception somewhere further up the stack and return unhappy in some form to which intervening layers of stack will respond (as control returns to them) by passing back the unhapiness without further ado. Note that a hunt through a tree for a first match to some key has the latter form, not the former.
If a judge returns something other than its input, the caller is going to be doing some kind of coping strategy (possibly just passing the buck to its own caller). The judge will usually have frobbed some data accessible from its input before returning so as to communicate its objection back up the ->up chain in the event of buck-passing: but the caller is at liberty to inspect that data and decide that it knows a way of coping with the judge's response.
[I think of this as the caller recognising the status returned by the judge as a sentencing magistrate disguised as a prison warder: if you don't recognise it, it obliges you to abandon what you were doing and come along quietly; but if you see through the disguise you can (try to) negotiate some other response. Never let the executive cheat the judiciary, whatever you do - always respect an unfamiliar sentencing magistrate, you never know when it'll turn out to be one powerful enough to end the universe if cheated.]
Although the front-line judges (the normal ones we'll be placing in proper status variables' ->how fields) will generally be `checkers' of one kind or another, there will be whackier judges around. Be careful not to transcribe them !
One common idiom I toy with: a judge which expects to be ->up->how from its input, in which case it accepts that status if its ->how is some particular judge (possibly the original judge, possibly the next judge up the chain from it, etc.). If it isn't the ->how of the given ->up, it just rejects the status by returning its ->up (or, if that is the status, some other status which you shouldn't recognise).
However, if it is the ->how of the ->up but isn't the ->how of the given status, it transcribes that ->how to ->up->how and marks ->up as invalid by pointing ->up->up at ->up (but maybe it recognises certain other ->how values and adjusts its behaviour for them ?). This is suitable for judging true status records (no private fields), such as the ones passed down by a caller to any tool with which it isn't intimately familiar: if the tool gets a bad status it doesn't recognise, from something it invokes, it hooks this status' ->up at the context status with which it was called and invokes the up's status on this chain, returning to its caller if it gets back anything other than the chain it passed in.
The status pointer Pull is intended to be the top of the grand status chain. Treat it with respect. Its ->how propagates a status up a chain of things with the same ->how.
#include "status.h"
status here = { signal, up }, try = catch(&here);
if (here.up == &here) { complain; }
else { success; }
The catch judge assumes that its input has been set up with a
`signal' as its ->how and a normal status as its ->up. If it catches the
signal, it points its input's ->up at the handler which caught it: otherwise,
it points its input's ->up at its input. This isn't quite the usual judge
protocol !
catch takes it on good faith that self->how is a signal to
be caught and its ->up is a normal status record (so catch may install a
proper judge in its ->how: eg, on failure, a judge who means uncaught). It
runs the ->up chain looking for status records whose ->how is catch: just
->up from each of these, it expects to find a matcher. To each matcher it
finds, it offers the status passed to catch for `preliminary recognition'. If
the matcher might accept the status, it returns happy, in response to which
catch points the status' ->up at the matcher and passes it to the matcher for
matching.
The matcher recognises the first call by the fact that it isn't the ->up->how of its input (which it will be on the second call): indeed, if catch finds a matcher whose ->how is the ->how of the normal status ->up from catch's input, it ignores the matcher. On the first call, the matcher doesn't look any further ->up the chain than suffices to check this. It returns the signal it is offered if it might match it, something else otherwise. In the latter case, catch carries on with its search of the ->up chain.
On the second call, the matcher has access to the matcher status as which it is invoked. (It won't have seen this the first time - this might not be the only instance of that matcher in the ->up chain, so hunting for it would have been foolish.) If it discovers, on looking at the private fields of the matcher, that it isn't going to catch the signal, it frobs the signal's ->up: in which case catch ignores what it returns, hangs the signal back off where it belongs and carries on searching the ->up chain.
If a matcher returns its input on its first call and doesn't frob ->up on its second, catch takes it to have caught the signal: whatever it returned on this second call, catch returns, leaving its input hanging off the matcher which matched it.
The caller of catch can tell whether catching succeeded by looking at the status it passed to catch: if it is its own ->up afterwards, it didn't get caught (and catch has returned its original ->up). Otherwise, its ->up is the matcher which caught it and catch has returned whatever that matcher returned on its second call.
To build a catcher:
<-\ /--------\ /--------\ /-------\ /-------\
{ pull, ^ }, <- { match, ^ }, <- { catch, ^ }, <- { pull, ^ }, <- { pull, ^ }, ...
caller | catching routine | delegation tool
wherein each pull is some normal judge (typically Pull->how) and match is a judge but not necessarily normal. When it catches, match might chose to unhook itself and its catch from the status chain: but it should leave the catching routine's pull in the chain for chain regularity's sake. The catching routine may chose to insert other status records (e.g. various kinds of sentinel) between its pull and its catch or ->up from its match, but catch and match must be adjacent.
The status ->up from catch, the matcher, is more interesting and gets called twice during match recognition, on the same record: the first time, this will be the foot of an ->up chain which leads to our catching routine section, above; the second time, its ->up will be the matcher itself. The matcher tells the difference between these by looking to see if it is the ->up->how of its input: it is on the second call but not on the first.
So if match is invoked on a status record whose ->up->how isn't match, it just decides (without looking further ->up the chain) whether it's interested in catching the status it has been given. If it says yes to this, the status record's ->up will be pointed at the matcher record and the matcher will again be invoked, now on a status record for which ->up->how is match.
When match is the ->up->how of the status record it's given, it can look at the details in ->up's private fields and, typically, those of the given status: using these, it'll decide whether it's really catching that status. If it isn't, it should leave the status alone except for frobbing its ->up, in response to which catch will restore the status's ->up to its old value and resume its journey up the status chain.
If match decides to catch the status it is given, it mustn't frob the ->up. If it returns (on its second call) the status it is given, catch will return happy and valid to its caller, with the status' ->up still pointing at the matcher which caught it. If it returns the matcher which was ->up from it on the second call, catch will point its ->up back where it used to be and return that. Otherwise, it hasn't frobbed ->up, but has returned something other than its input and the ->up of that. In this case, catch transcribes the ->how of this return (assumed proper) to the ->how of its input's original ->up (assumed normal) and returns this.
Note that this machinery lets catch use catchers that aren't on the stack as auto status records - it can run off the top of the stack into the static machine that underpins everything ...
If ready doesn't give you back the status record you gave it, abandon its context. Otherwise, run what checks you will on it and take for granted that it, its ->up and the ->how of each of these are all non-NULL, with the input distinct from its ->up.
It is intended as a pre-test for use at the start of any judge: very few judges will have any reason at all to try to handle a status it has rejected.
One thing you should do before passing any status record to any judge is to check that the record's ->up isn't pointing back at the record itself. If it is, it is invalid and any judge should reject it: judges will typically do so by calling ready first and returning its result because ready is unhappy. The result in this case is very unfriendly.
[Note that, unless its input is NULL, ready will leave its input valid except, possibly, for having its ->up pointed at itself. If its ->up was originally pointed at itself, ready will point it at the same very nasty status it'll be returning.]
If ready's answer is its input, you may have some checking you want to do to this input - call this `self' - but you can safely assume self, self->up, self->how and self->up->how are all non-NULL, with self and self->up distinct. Notice that other judges, after calling ready and getting self back, may chose to frob self->up to point at self, possibly even when returning self: but if ready frobs self in any way, it returns something else.
If ready's answer isn't self, contrariwise, self->up generally will be self after it returns - unless it was before-hand, in which case ready's answer is very nasty indeed and is now self->up: you don't recognise it, though, so you aren't going to invoke it. If self->up wasn't self when you called ready, even it might have had its ->up pointed back on itself (if its ->how was NULL or its ->up was self).
When returning its input, ready does not change it (or anything ->up from it), so any changes you find in this case were made before you called ready.
[If the status returned by ready used to be self->up, and used to have ->how == NULL, this will also have been `mended' and then marked as invalid by having its ->up point at itself. Likewise, if self->up->up is self, it is `mended' to self->up, thereby marking self->up as invalid. These are robustness measures and I might chose to do some variety of `mending' which would turn them into bombs: you should be sure in any case not to exercise them.] Always check for changes to ->up.
If ready really doesn't like self, it sentences you to death and makes sure the end of the universe knows how to happen, which it will if you invoke the status ready returned to you. So be sure you don't invoke ready's answer unless you recognise it. Generally, this will not be an issue: you'll have given up already.
Note that, once the end of the universe gets under way, ready will change behaviour: it will reject everything.
A typical piece of code will use an auto status record, here, as its context, chained with its calling context as here.up: it'll pass &here down as context to some tool and detect whether here.up has changed afterwards. If there's no change, it'll take it the tool succeeded and carry on with what it's doing.
If here.up has changed, the piece of code using here as context may simply chose to abort, but more usually it's going to ask a judge (typically the one from its caller's context) for advice. Every judge will reject here unless here.up and &here differ: so, before delegating to a judge, you must check whether here.up has changed to &here; if that doesn't persuade you not to go to court, you must reset here.up to something, typically your calling context. That might also influence you choice of which judge to call on the status, though setting here.up may be how you specify the choice.
On calling judge(&here) with here.up sane, your code should compare the result with &here. If it isn't &here, simply abort here's context - let the next context up the chain cope with whatever messages it's been given by the judge who just rejected your status.
If judge(&here) did return here, check whether here.up is sane.
: this may influence its choice of judge with which to resolve the situation but here's context must point it to something else before calling any judge: if what it's changed to is something else, it should typically leave it alone; in each case it should call a judge, typically the one it originally put in here.how or received as the ->how of its context. If this judge(&here) returns unhappy (which it will if here.up doesn't provide a context chain) you should abandon here's context - which typically means returning control to your caller.
The mainstay of the judiciary is the discipline controlling when a status record is frobbed.
When changing the ->how of a status,
The base protocol for communicating this last message is to frob ->up: the judiciary supplements this internally with the judgement protocol, in which a judge returns its input on success, something else on failure. It may still frob its input's ->up even on success - if it does, its spec should say what this will mean !
These are only private to this degree: any code which is familiar with the ->how of a status record is at liberty to frob its private fields. All judges are familiar enough with one another to frob one another's ->up pointers and no judge ever frobs another's ->how without frobbing some other field(s) with which both it and the prior ->how's peers are familiar: this will usually mean frobbing ->up.
Status records will typically inhabit the stack as auto variables. They will sometimes be statics. If you chase the ->up of a static you'll either find NULL or another static: that's a Rule that all code using status variables should keep. If you chase the ->up of an auto, you have the added possibility of finding an auto and, indeed, this will typically be a status record that `came onto the stack before' the auto whose ->up it is.
I'll describe a status as `terminal' if it is its own ->up or its ->up is NULL. Never pass a terminal (or NULL) status to a judge: it needs the ->up of what it's given, to use it as context. Indeed a judge may fairly take exception to finding itself different from the ->up->how of what it's given: it expects to be judging in a context over which it has jurisdiction.
When a judge is called on some status, case, and case->up->how is the judge in question, that judge has final authority over the case. That includes the ability to change its ->up, the ->how of its ->up and possibly the contents of some private ...
From any status record we can chase ->up a chain of status records: every status record passed to Every chain of status records that ever gets passed to a judge must run up to a terminal status and must go up at least one step before getting there.
If you want some dynamic (heap) memory involved, hang it off a status record's private fields, being sure to
in short, all the obvious stuff. I don't think it's wise to put allocated records in the ->up chain of an auto. Whatever: just don't go free()ing status records unless you really know they're yours and you allocated them.
Routine use of a status will be as context for a function call of some kind.
Specifically, every function inside this toy engine will have a
status * as its first argument, to be understood as the `context'
in which the function is being called. It's name will generally be `up'.
Here's the archetypical invocation:
status here = { up->how, up };
record ret = func->how(&here, some, args);
if (here.up == up) { success; }
This will have, as an else clause, some `coping' strategy: if this fails, we frob up->up and return to our caller, for whom our `up' is the address of a local auto, just as &here was when we called func->how. [I used to use frobbing of ->how as the signal, so some old code will still do that for a while. Under the new regime, by contrast ...] I won't be frobbing ->how unless I also frob ->up.
In a coping strategy, when here.up has been frobbed, we can look at its new value and at here.how (which might also have been changed). If we recognise either or both, that may give us the information we need to know how to cope without delegating: or we may chose to delegate the decision on what to do about the situation. The one constraint is that, if our coping strategy fails, we flag this by frobbing up->up before returning.
The default way of frobbing up->up is to set it to up: a status variable which is its own ->up is a nice lightweight way of saying `oops'. Just be sure not to do it to a static status unless there's someone on the stack who knows you'll be doing that and can detect and correct if necessary. This, of course, is one of the things we should turn out to be able to do. It just isn't built in, so can't be taken for granted.
The flipside of that lightweight `oops' is that anyone calling a judge on a status, self, has a duty to ensure that self->up is not self: it is always contempt of court to pass a judge a status which is its own ->up.
The -> up chain from a status serves as a `model' for a call-stack (the upper reaches of which may be virtual). It marks its top as a status with top->up equal to either top or NULL. The archetype for running up the chain is thus
status *run;
for (run = initial; run; run = run->up) {
operate;
if (run == run->up) break;
}
There are two ways I'll call a judge: one is on a status whose ->up->how is the judge.
else {
status *back = up->up;
up->up = here.up;
here.up = up;
if (negotiate(&here, back, func, some, args)) { cope; }
else if (JUDGE(&here) || JUDGE(up)) return ret;
else if (back == here.up)
}
A judge decides whether a status is a sensible thing with which to continue. Judging a status amounts to deciding whether something we just did has `succeeded'. The condition for success is that the judge returns the same value it was given. If it doesn't, you might want to check what it did return - the calling code may know this judge and know how to talk it into rehabilitating the situation. Otherwise, chose some party to what you did to get your status in that mess, return that, and trust the judge to have made arrangements for your caller to decide what to do about the situation.
The default judge, known as ready, returns self if self->how is ready. Failing that, if self->up->how isn't ready it returns self->up, unless this is self, in which case it returns something else: it really doesn't like this situation.
If a judge is called with self == context, it may chose to return some other (typically static) status object: you should
which is to be used to judge a context subordinate to the given self == context. This is the hook by which judges delegate. The default action will be to return the status pointer given.
Otherwise, a judge who finds self->how == context->how will return self, meaning `all is well'. The judge may chose to return self in some other cases, possibly after doing things to self and, potentially, to status records it finds, by chasing ->up in search of an ->how it recognises.
what follows is at least one revolution out of date.
It's the protocol for exception handling `under the bonnet' and it's very simple. You'll be making a direct call (in a context with status *up and judge beforefn) to a record as:
status back = { beforefn, (record) up }, here = { beforefn, &back };
record val = arecord->how(&here, ...);
if JUDGE(&here) return val;
The behaviour of a judge, such as beforefn, is: it returns a status which it believes can handle what it's found in here; it may modify here in any case, but if it modifies up it will return up. If the status it returns is here, (well, its address, of course) it means we can carry on happily.
In principle (eg when up belongs to `the next scope out' in the present function, rather than to the function's caller), the code above could replace its third line with
status *help = beforefn(up, &here);
if (help == up) break;
else if (help != &here) return val;
in which I'm using break as a token for `pop out to a scope which owns up', eg because we're in the body of a loop. However, if you need to do that, maybe it's the code's way of suggesting you break the inner scope out as a function ...
The judge passed back by Initialise works as follows. If it finds itself equal to here.how, it returns &here. If here->what is up, it decides to believe that you're participating in a protocol in which each status has, as its what, the status of an enclosing context, to which it'll be returning control if it can't cope with some error. In that case, the judge runs up the chain until it finds a how other than ready (which may be up->how, of course): it then delegates to that judge. Otherwise, it transcribes here to up (except that if here.what is &here it transcribes the ->how but sets up->what to up) and returns up.
Chose your beforefn with care. The `ready' judge returned by Initialise is a generic fall-back: indeed, the usual beforefn will be up->how, with up->what telling you `where you are' in a grand chain right back to that status record that Initialise stored in your context's what field. There will be other judges, though.
Note that this means that, to invoke a record, you need to pass in a status record that's set up right: the how you invoke on the record can then assume a nice prior state which in which it can leave ready and the `top' of the status-chain to sort out issues that I don't want to fight with all the time.
Suppose nothing in your entire program is going to cope with the problem: we're going to run right up the stack, chasing status pointers, and hit the static stuff I embed at the top. Putatively, the very top of the chain just has an exception handler which calls exit(), but that's not going to be very informative. So we can install a piece of machinery just below that one which handles traceback machinery. It'll get called when everything's about to go pear-shaped, and this will give it the chance to frob the judges on the entire chain back up to it before everyone returns and drops that chain off the stack.
It can collect information while it's doing that frobbing, of course, but it's probably best for it to leave the information gathering to the return process: and it only really needs to install a well-chosen judge on the bottom of the chain and let the normal process resume, passing the buck and returning but, at each step of the way, calling the judge to find out how we're doing - and, maybe, printing messages.
All that machinery can be sat there in the foundations without getting in the way until it's needed: if someone else is going to catch the relevant error anyway, it doesn't get invoked. Likewise, a signal handler can install its suitable judge on the bottom of the chain that's raised the signal, knowing that all that remains is for lots of static data to get unrolled (cleanly, giving functions a chance to do clean-up on the way back out) until we hit one with the appropriate pardon for the signal's judge.
Written by Eddy.