Lichen

Annotated templates/cexcept.h

694:734bddc017db
2017-03-10 Paul Boddie Prevent __data__ accesses via anonymous accessors from trying to find the attribute on classes since __data__ is always provided by the immediate object and because the check_and_load_via_any operation uses a null value internally to detect failure that can be confused with a genuine __data__ attribute with a zero value (such as the integer zero).
paul@143 1
/*===
paul@143 2
cexcept.h 2.0.1-Lichen (2016-Oct-27-Thu)
paul@143 3
A modified form of...
paul@143 4
cexcept.h 2.0.1 (2008-Jul-19-Sat)
paul@143 5
http://www.nicemice.net/cexcept/
paul@143 6
Adam M. Costello
paul@143 7
http://www.nicemice.net/amc/
paul@143 8
paul@143 9
An interface for exception-handling in ANSI C (C89 and subsequent ISO
paul@143 10
standards), developed jointly with Cosmin Truta. 
paul@143 11
paul@189 12
    Copyright (c) 2016 Paul Boddie (modified for Lichen).
paul@143 13
    Copyright (c) 2000-2008 Adam M. Costello and Cosmin Truta.
paul@143 14
    This software may be modified only if its author and version
paul@143 15
    information is updated accurately, and may be redistributed
paul@143 16
    only if accompanied by this unaltered notice.  Subject to those
paul@143 17
    restrictions, permission is granted to anyone to do anything
paul@143 18
    with this software.  The copyright holders make no guarantees
paul@143 19
    regarding this software, and are not responsible for any damage
paul@143 20
    resulting from its use.
paul@143 21
paul@143 22
The cexcept interface is not compatible with and cannot interact
paul@143 23
with system exceptions (like division by zero or memory segmentation
paul@143 24
violation), compiler-generated exceptions (like C++ exceptions), or
paul@143 25
other exception-handling interfaces.
paul@143 26
paul@143 27
When using this interface across multiple .c files, do not include
paul@143 28
this header file directly.  Instead, create a wrapper header file that
paul@143 29
includes this header file and then invokes the define_exception_type
paul@143 30
macro (see below).  The .c files should then include that header file.
paul@143 31
paul@143 32
The interface consists of one type, one well-known name, and six macros.
paul@143 33
paul@143 34
paul@143 35
define_exception_type(type_name);
paul@143 36
paul@143 37
    This macro is used like an external declaration.  It specifies
paul@143 38
    the type of object that gets copied from the exception thrower to
paul@143 39
    the exception catcher.  The type_name can be any type that can be
paul@143 40
    assigned to, that is, a non-constant arithmetic type, struct, union,
paul@143 41
    or pointer.  Examples:
paul@143 42
paul@143 43
        define_exception_type(int);
paul@143 44
paul@143 45
        enum exception { out_of_memory, bad_arguments, disk_full };
paul@143 46
        define_exception_type(enum exception);
paul@143 47
paul@143 48
        struct exception { int code; const char *msg; };
paul@143 49
        define_exception_type(struct exception);
paul@143 50
paul@143 51
    Because throwing an exception causes the object to be copied (not
paul@143 52
    just once, but twice), programmers may wish to consider size when
paul@143 53
    choosing the exception type.
paul@143 54
paul@143 55
paul@143 56
struct __exception_context;
paul@143 57
paul@143 58
    This type may be used after the define_exception_type() macro has
paul@143 59
    been invoked.  A struct __exception_context must be known to both
paul@143 60
    the thrower and the catcher.  It is expected that there be one
paul@143 61
    context for each thread that uses exceptions.  It would certainly
paul@143 62
    be dangerous for multiple threads to access the same context.
paul@143 63
    One thread can use multiple contexts, but that is likely to be
paul@143 64
    confusing and not typically useful.  The application can allocate
paul@143 65
    this structure in any way it pleases--automatic, static, or dynamic.
paul@143 66
    The application programmer should pretend not to know the structure
paul@143 67
    members, which are subject to change.
paul@143 68
paul@143 69
paul@143 70
struct __exception_context *__the_exception_context;
paul@143 71
paul@143 72
    The __Try/__Catch and __Throw statements (described below) implicitly
paul@143 73
    refer to a context, using the name __the_exception_context.  It is
paul@143 74
    the application's responsibility to make sure that this name yields
paul@143 75
    the address of a mutable (non-constant) struct __exception_context
paul@143 76
    wherever those statements are used.  Subject to that constraint, the
paul@143 77
    application may declare a variable of this name anywhere it likes
paul@143 78
    (inside a function, in a parameter list, or externally), and may
paul@143 79
    use whatever storage class specifiers (static, extern, etc) or type
paul@143 80
    qualifiers (const, volatile, etc) it likes.  Examples:
paul@143 81
paul@143 82
        static struct __exception_context
paul@143 83
          * const __the_exception_context = &foo;
paul@143 84
paul@143 85
        { struct __exception_context *__the_exception_context = bar; ... }
paul@143 86
paul@143 87
        int blah(struct __exception_context *__the_exception_context, ...);
paul@143 88
paul@143 89
        extern struct __exception_context __the_exception_context[1];
paul@143 90
paul@143 91
    The last example illustrates a trick that avoids creating a pointer
paul@143 92
    object separate from the structure object.
paul@143 93
paul@143 94
    The name could even be a macro, for example:
paul@143 95
paul@143 96
        struct __exception_context ec_array[numthreads];
paul@143 97
        #define __the_exception_context (ec_array + thread_id)
paul@143 98
paul@143 99
    Be aware that __the_exception_context is used several times by the
paul@143 100
    __Try/__Catch/__Throw macros, so it shouldn't be expensive or have side
paul@143 101
    effects.  The expansion must be a drop-in replacement for an
paul@143 102
    identifier, so it's safest to put parentheses around it.
paul@143 103
paul@143 104
paul@143 105
void __init_exception_context(struct __exception_context *ec);
paul@143 106
paul@143 107
    For context structures allocated statically (by an external
paul@143 108
    definition or using the "static" keyword), the implicit
paul@143 109
    initialization to all zeros is sufficient, but contexts allocated
paul@143 110
    by other means must be initialized using this macro before they
paul@143 111
    are used by a __Try/__Catch statement.  It does no harm to initialize
paul@143 112
    a context more than once (by using this macro on a statically
paul@143 113
    allocated context, or using this macro twice on the same context),
paul@143 114
    but a context must not be re-initialized after it has been used by a
paul@143 115
    __Try/__Catch statement.
paul@143 116
paul@143 117
paul@143 118
__Try statement
paul@143 119
__Catch (expression) statement
paul@143 120
paul@143 121
    The __Try/__Catch/__Throw macros are capitalized in order to avoid
paul@143 122
    confusion with the C++ keywords, which have subtly different
paul@143 123
    semantics.
paul@143 124
paul@143 125
    A __Try/__Catch statement has a syntax similar to an if/else statement,
paul@143 126
    except that the parenthesized expression goes after the second
paul@143 127
    keyword rather than the first.  As with if/else, there are two
paul@143 128
    clauses, each of which may be a simple statement ending with a
paul@143 129
    semicolon or a brace-enclosed compound statement.  But whereas
paul@143 130
    the else clause is optional, the __Catch clause is required.  The
paul@143 131
    expression must be a modifiable lvalue (something capable of being
paul@143 132
    assigned to) of the same type (disregarding type qualifiers) that
paul@143 133
    was passed to define_exception_type().
paul@143 134
paul@143 135
    If a __Throw that uses the same exception context as the __Try/__Catch is
paul@143 136
    executed within the __Try clause (typically within a function called
paul@143 137
    by the __Try clause), and the exception is not caught by a nested
paul@143 138
    __Try/__Catch statement, then a copy of the exception will be assigned
paul@143 139
    to the expression, and control will jump to the __Catch clause.  If no
paul@143 140
    such __Throw is executed, then the assignment is not performed, and
paul@143 141
    the __Catch clause is not executed.
paul@143 142
paul@143 143
    The expression is not evaluated unless and until the exception is
paul@143 144
    caught, which is significant if it has side effects, for example:
paul@143 145
paul@143 146
        __Try foo();
paul@143 147
        __Catch (p[++i].e) { ... }
paul@143 148
paul@143 149
    IMPORTANT: Jumping into or out of a __Try clause (for example via
paul@143 150
    return, break, continue, goto, longjmp) is forbidden--the compiler
paul@143 151
    will not complain, but bad things will happen at run-time.  Jumping
paul@143 152
    into or out of a __Catch clause is okay, and so is jumping around
paul@143 153
    inside a __Try clause.  In many cases where one is tempted to return
paul@143 154
    from a __Try clause, it will suffice to use __Throw, and then return
paul@143 155
    from the __Catch clause.  Another option is to set a flag variable and
paul@143 156
    use goto to jump to the end of the __Try clause, then check the flag
paul@143 157
    after the __Try/__Catch statement.
paul@143 158
paul@143 159
    IMPORTANT: The values of any non-volatile automatic variables
paul@143 160
    changed within the __Try clause are undefined after an exception is
paul@143 161
    caught.  Therefore, variables modified inside the __Try block whose
paul@143 162
    values are needed later outside the __Try block must either use static
paul@143 163
    storage or be declared with the "volatile" type qualifier.
paul@143 164
paul@143 165
paul@143 166
__Throw expression;
paul@143 167
paul@143 168
    A __Throw statement is very much like a return statement, except that
paul@143 169
    the expression is required.  Whereas return jumps back to the place
paul@143 170
    where the current function was called, __Throw jumps back to the __Catch
paul@143 171
    clause of the innermost enclosing __Try clause.  The expression must
paul@143 172
    be compatible with the type passed to define_exception_type().  The
paul@143 173
    exception must be caught, otherwise the program may crash.
paul@143 174
paul@143 175
    Slight limitation:  If the expression is a comma-expression, it must
paul@143 176
    be enclosed in parentheses.
paul@143 177
paul@143 178
paul@143 179
__Try statement
paul@143 180
__Catch_anonymous statement
paul@143 181
paul@143 182
    When the value of the exception is not needed, a __Try/__Catch statement
paul@143 183
    can use __Catch_anonymous instead of __Catch (expression).
paul@143 184
paul@143 185
paul@143 186
Everything below this point is for the benefit of the compiler.  The
paul@143 187
application programmer should pretend not to know any of it, because it
paul@143 188
is subject to change.
paul@143 189
paul@143 190
===*/
paul@143 191
paul@143 192
paul@143 193
#ifndef CEXCEPT_H
paul@143 194
#define CEXCEPT_H
paul@143 195
paul@143 196
paul@143 197
#include <setjmp.h>
paul@143 198
paul@143 199
#define define_exception_type(etype) \
paul@143 200
struct __exception_context { \
paul@143 201
  jmp_buf *penv; \
paul@143 202
  int caught; \
paul@143 203
  volatile struct { etype etmp; } v; \
paul@143 204
}
paul@143 205
paul@143 206
/* etmp must be volatile because the application might use automatic */
paul@143 207
/* storage for __the_exception_context, and etmp is modified between   */
paul@143 208
/* the calls to setjmp() and longjmp().  A wrapper struct is used to */
paul@143 209
/* avoid warnings about a duplicate volatile qualifier in case etype */
paul@143 210
/* already includes it.                                              */
paul@143 211
paul@143 212
#define __init_exception_context(ec) ((void)((ec)->penv = 0))
paul@143 213
paul@143 214
#define __Try \
paul@143 215
  { \
paul@143 216
    jmp_buf *exception__prev, exception__env; \
paul@143 217
    exception__prev = __the_exception_context->penv; \
paul@143 218
    __the_exception_context->penv = &exception__env; \
paul@143 219
    if (setjmp(exception__env) == 0) { \
paul@143 220
      do
paul@143 221
paul@143 222
#define exception__catch(action) \
paul@143 223
      while (__the_exception_context->caught = 0, \
paul@143 224
             __the_exception_context->caught); \
paul@143 225
    } \
paul@143 226
    else { \
paul@143 227
      __the_exception_context->caught = 1; \
paul@143 228
    } \
paul@143 229
    __the_exception_context->penv = exception__prev; \
paul@143 230
  } \
paul@143 231
  if (!__the_exception_context->caught || action) { } \
paul@143 232
  else
paul@143 233
paul@143 234
#define __Catch(e) exception__catch(((e) = __the_exception_context->v.etmp, 0))
paul@143 235
#define __Catch_anonymous exception__catch(0)
paul@143 236
paul@143 237
/* __Try ends with do, and __Catch begins with while(0) and ends with     */
paul@143 238
/* else, to ensure that __Try/__Catch syntax is similar to if/else        */
paul@143 239
/* syntax.                                                            */
paul@143 240
/*                                                                    */
paul@143 241
/* The 0 in while(0) is expressed as x=0,x in order to appease        */
paul@143 242
/* compilers that warn about constant expressions inside while().     */
paul@143 243
/* Most compilers should still recognize that the condition is always */
paul@143 244
/* false and avoid generating code for it.                            */
paul@143 245
paul@143 246
#define __Throw \
paul@143 247
  for (;; longjmp(*__the_exception_context->penv, 1)) \
paul@143 248
    __the_exception_context->v.etmp =
paul@143 249
paul@143 250
paul@143 251
#endif /* CEXCEPT_H */