summaryrefslogtreecommitdiff
path: root/fitz/fitz.h
diff options
context:
space:
mode:
authorRobin Watts <robin.watts@artifex.com>2012-01-09 14:23:27 +0000
committerRobin Watts <robin.watts@artifex.com>2012-01-09 19:58:42 +0000
commit6de45df10e024813ad7c62f31394f86fbcb364a7 (patch)
tree3e5f397211a8dd745b1ec91c79e68183b349a1b3 /fitz/fitz.h
parentc3f257e0317e1c7fba13f2d2f9ae50f3a4ae442d (diff)
downloadmupdf-6de45df10e024813ad7c62f31394f86fbcb364a7.tar.xz
Update fz_try/fz_catch to be fz_try/{fz_always}/fz_catch.
Add explanations of how to use the macros in fitz.h. Also included are 2 different formulations, with different strengths/ weaknesses for reference. Will remove these shortly, but I want a reference to them in git. Workaround bug in Mac OS Lion gcc (clang works fine).
Diffstat (limited to 'fitz/fitz.h')
-rw-r--r--fitz/fitz.h197
1 files changed, 194 insertions, 3 deletions
diff --git a/fitz/fitz.h b/fitz/fitz.h
index a5695d56..7788ffd6 100644
--- a/fitz/fitz.h
+++ b/fitz/fitz.h
@@ -128,6 +128,7 @@ struct fz_error_context_s
{
int top;
struct {
+ int code;
jmp_buf buffer;
} stack[256];
char message[256];
@@ -136,16 +137,206 @@ struct fz_error_context_s
void fz_var_imp(void *);
#define fz_var(var) fz_var_imp((void *)&(var))
+/* MuPDF uses an set of exception handling macros to simplify error return
+ * and cleanup. Conceptually, they work a lot like C++'s try/catch system,
+ * but do not require any special compiler support.
+ *
+ * The basic formulation is as follows:
+ *
+ * fz_try(ctx)
+ * {
+ * // Try to perform a task. Never 'return', 'goto' or 'longjmp' out
+ * // of here. 'break' may be used to safely exit (just) the try block
+ * // scope.
+ * }
+ * fz_always(ctx)
+ * {
+ * // Any code here is always executed, regardless of whether an
+ * // exception was thrown within the try or not. Never 'return', 'goto'
+ * // or longjmp out from here. 'break' may be used to safely exit (just)
+ * // the always block scope.
+ * }
+ * fz_catch(ctx)
+ * {
+ * // This code is called (after any always block) only if something
+ * // within the fz_try block (including any functions it called) threw
+ * // an exception. The code here is expected to handle the exception
+ * // (maybe record/report the error, cleanup any stray state etc) and
+ * // can then either exit the block, or pass on the exception to a
+ * // higher level (enclosing) fz_try block (using fz_throw, or
+ * // fz_rethrow).
+ * }
+ *
+ * The fz_always block is optional, and can safely be omitted.
+ *
+ * The macro based nature of this system has 3 main limitations:
+ *
+ * 1) Never return from within try (or 'goto' or longjmp out of it).
+ * This upsets the internal housekeeping of the macros and will cause
+ * problems later on. The code will detect such things happening, but
+ * by then it is too late to give a helpful error report as to where the
+ * original infraction occurred.
+ *
+ * 2) The fz_try(ctx) { ... } fz_always(ctx) { ... } fz_catch(ctx) { ... }
+ * is not one atomic C statement. That is to say, if you do:
+ *
+ * if (condition)
+ * fz_try(ctx) { ... }
+ * fz_catch(ctx) { ... }
+ *
+ * then you will not get what you want. Use the following instead:
+ *
+ * if (condition) {
+ * fz_try(ctx) { ... }
+ * fz_catch(ctx) { ... }
+ * }
+ *
+ * 3) The macros are implemented using setjmp and longjmp, and so the standard
+ * C restrictions on the use of those functions apply to fz_try/fa_catch
+ * too. In particular, any "truly local" variable that is set between the
+ * start of fz_try and something in fz_try throwing an exception may become
+ * undefined as part of the process of throwing that exception.
+ *
+ * As a way of mitigating this problem, we provide an fz_var() macro that
+ * tells the compiler to ensure that that variable is not unset by the
+ * act of throwing the exception.
+ *
+ * A model piece of code using these macros then might be:
+ *
+ * house build_house(plans *p)
+ * {
+ * material m = NULL;
+ * walls w = NULL;
+ * roof r = NULL;
+ * house h = NULL;
+ * tiles t = make_tiles();
+ *
+ * fz_var(w);
+ * fz_var(r);
+ * fz_var(h);
+ *
+ * fz_try(ctx)
+ * {
+ * fz_try(ctx)
+ * {
+ * m = make_bricks();
+ * }
+ * fz_catch(ctx)
+ * {
+ * // No bricks available, make do with straw?
+ * m = make_straw();
+ * }
+ * w = make_walls(m, p);
+ * r = make_roof(m, t);
+ * h = combine(w, r); // Note, NOT: return combine(w,r);
+ * }
+ * fz_always(ctx)
+ * {
+ * drop_walls(w);
+ * drop_roof(r);
+ * drop_material(m);
+ * drop_tiles(t);
+ * }
+ * fz_catch(ctx)
+ * {
+ * fz_throw(ctx, "build_house failed");
+ * }
+ * return h;
+ * }
+ *
+ * Things to note about this:
+ *
+ * a) If make_tiles throws an exception, this will immediately be handled
+ * by some higher level exception handler. If it succeeds, t will be
+ * set before fz_try starts, so there is no need to fz_var(t);
+ *
+ * b) We try first off to make some bricks as our building material. If
+ * this fails, we fall back to straw. If this fails, we'll end up in
+ * the fz_catch, and the process will fail neatly.
+ *
+ * c) We assume in this code that combine takes new reference to both the
+ * walls and the roof it uses, and therefore that w and r need to be
+ * cleaned up in all cases.
+ *
+ * d) We assume the standard C convention that it is safe to destroy
+ * NULL things.
+ */
+
+/* Exception macro definitions. Just treat these as a black box - pay no
+ * attention to the man behind the curtain. */
#define fz_try(ctx) \
if (fz_push_try(ctx->error), \
- !setjmp(ctx->error->stack[ctx->error->top].buffer)) \
+ (ctx->error->stack[ctx->error->top].code = setjmp(ctx->error->stack[ctx->error->top].buffer)) == 0) \
{ do {
+#define fz_always(ctx) \
+ } while (0); \
+ } \
+ { do { \
+
#define fz_catch(ctx) \
} while(0); \
- ctx->error->top--; \
} \
- else
+ if (ctx->error->stack[ctx->error->top--].code)
+
+/*
+ * We also include a couple of other formulations of the macros, with
+ * different strengths and weaknesses. These will be removed shortly, but
+ * I want them in git for at least 1 revision so I have a record of them.
+ *
+ * A formulation of try/always/catch that lifts limitation 2 above, but
+ * has problems when try/catch are nested in the same function; the inner
+ * nestings need to use fz_always_(ctx, label) and fz_catch_(ctx, label)
+ * instead. This was held as too high a price to pay to drop limitation 2.
+ *
+ * #define fz_try(ctx) \
+ * if (fz_push_try(ctx->error), \
+ * (ctx->error->stack[ctx->error->top].code = setjmp(ctx->error->stack[ctx->error->top].buffer)) == 0) \
+ * { do {
+ *
+ * #define fz_always_(ctx, label) \
+ * } while (0); \
+ * goto ALWAYS_LABEL_ ## label ; \
+ * } \
+ * else if (ctx->error->stack[ctx->error->top].code) \
+ * { ALWAYS_LABEL_ ## label : \
+ * do {
+ *
+ * #define fz_catch_(ctx, label) \
+ * } while(0); \
+ * if (ctx->error->stack[ctx->error->top--].code) \
+ * goto CATCH_LABEL_ ## label; \
+ * } \
+ * else if (ctx->error->top--, 1) \
+ * CATCH_LABEL ## label:
+ *
+ * #define fz_always(ctx) fz_always_(ctx, TOP)
+ * #define fz_catch(ctx) fz_catch_(ctx, TOP)
+ *
+ * Another alternative formulation, that again removes limitation 2, but at
+ * the cost of an always block always costing us 1 extra longjmp per
+ * execution. Again this was felt to be too high a cost to use.
+ *
+ * #define fz_try(ctx) \
+ * if (fz_push_try(ctx->error), \
+ * (ctx->error->stack[ctx->error->top].code = setjmp(ctx->error->stack[ctx->error->top].buffer)) == 0) \
+ * { do {
+ *
+ * #define fz_always(ctx) \
+ * } while (0); \
+ * longjmp(ctx->error->stack[ctx->error->top].buffer, 3); \
+ * } \
+ * else if (ctx->error->stack[ctx->error->top].code & 1) \
+ * { do {
+ *
+ * #define fz_catch(ctx) \
+ * } while(0); \
+ * if (ctx->error->stack[ctx->error->top].code == 1) \
+ * longjmp(ctx->error->stack[ctx->error->top].buffer, 2); \
+ * ctx->error->top--;\
+ * } \
+ * else if (ctx->error->top--, 1)
+ */
void fz_push_try(fz_error_context *ex);
void fz_throw(fz_context *, char *, ...) __printflike(2, 3);