Karim BELABAS on Wed, 11 Sep 2002 04:56:14 +0200 (MEST)


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

Re: error recovery (was Re: gp: series bug)


On Wed, 11 Sep 2002, Iwao KIMURA wrote:
> BTW, does anybody tell me how to do error-recovery ?
> The way I took (and am taking) is
> (0) define the function
> void
> pariErrdie(void)
> {
>   longjmp (buf, 1); /* jmp_buf buf is declared somewhere globally */
>   return;
> }
>
> (1) In the eary stage (after calling pari_init()), we put
> pariErr->die = pariErrdie;
>
> (2) Every time I want to call Pari library function,
>
>     ltop = avma;
>     if (!setjmp (buf))
>       {
>         /* call Pari library function */
> 	parifunc (some, args, here);
>       } else {
>        /* clear up */
>        avma = ltop;
>      }

This does not work well. Assume f1 and f2 use this mechanism,

  f1()
  {
    if (!setjmp(buf))
    {

    } else {
      f2();
      ...;
    }
  }

f2() overwrites 'buf', returns, and now 'buf' refers to an invalid stack
environment. At this point, calling pariErrdie crashes the program.

===========================================================================

The error recovery code in libpari is experimental (hence not documented),
and I've just modified it in CVS a few hours ago, while investigating some of
Igor's bug reports [ funny you'd ask just now! ].

I wanted to encapsulate all the code in the library using (something looking
like) the trap() mechanism. So I defined a set of 3 macros CATCH / TRY /
ENDCATCH to hide more complicated routines.

Let 'numerr' be a pari error number from parierr.h (trap the specified
exception), or a negative number (trap everything). Exceptions: you can't
trap memer (= no memory left) to prevent oo recursion, and you can't trap
Warnings, only errors.

The following tries the 'code' block, jumping to 'recovery' if
exception 'numerr' is raised within 'code' (or in fact within 'recovery'
itself!)

  CATCH(numerr) {
    /* recovery */
  } TRY {
    /* code */
  } ENDCATCH;

IMPORTANT: the code between CATCH / ENDCATCH is _not_ allowed to use any
direct flow control instruction to jump out of the protected code [ longjmp,
break, return, goto, etc. ]. It is OK to fool around within the protected
area. Getting out using the PARI err() function is also allowed [ e.g. if an
exception is raised from 'code', not caught by this (or a deeper) block ],
since it also performs the required cleanup.

Why? Jumping out would skip the cleanup code hidden in the ENDCATCH macro,
leaking an error handler refering to a stack environment that is no longer
valid [ = time bomb that will blow up as soon as the error occurs again, if
no newer handler is there to intercept it ].

Note: I could provide another macro, says CATCH_RELEASE(), that you could
invoke if you insist on getting out [ and would execute the ENDCATCH cleanup
code ]. It is not needed in pari and I don't think I want it.

There's a less useful variant CATCH / RETRY / ENDCATCH:
  CATCH(numerr)
  {

  } /* fall through */
  RETRY
  {

  } ENDCATCH;

where the offending code is retried after the recovery code, still trapping
the specified errors of course.

CATCH/TRY blocks can be embedded without adverse effect: a stack of handlers
is maintained for each error, the most recent handler, be it specific
(numerr >= 0) or generic (numerr < 0), being activated.

There's no simple way to trap an arbitrary set of exceptions in a given
block; either everything, or a single error.


What I _could_ do (this is trivial, I simply did not need it to encapsulate
the existing PARI code, and I just thought about it when writing the above),
is provide a 'pari_errno' (automatic variable valid in the CATCH/ENDCATCH
block). that would be set to the actual exception raised, so that you could

  CATCH(-1)
  {
    switch (pari_errno)
    {
      case primer1: ...
      case precer: ...
      ...
    }
  } TRY {

  } ENDCATCH;

This does not prevent the longjmp() from getting you out of the TRY block, it
only allows for different kinds of cleanup depending on the error, in a nicer
way than embedding 10 different CATCH/TRY blocks.

This is the internal mechanism used by trap() when invoked in the following
guise:

(22:06) gp > for(i=-2,2, print(trap(/*all*/, /*catch*/ oo, /*try*/ 1/i)))
-1/2
-1
oo
1
1/2

[ there are more general/flexible routines, the macros only encapsulate the
most frequent situation, which is quite close to your code above ]

If you forget one of the 3 macros, you will get very weird parse errors.

I don't guarantee stability yet.

Have fun,

    Karim.

PS: for those interested, here are the macros (paricom.h) [
err_catch/err_leave in language/init.c basically push down / pop out handlers
from a global stack ]

#define CATCH(err) {         \
  VOLATILE long __err = err; \
  jmp_buf __env;             \
  void *__catcherr;          \
  if (setjmp(__env))

#define RETRY \
  { __catcherr = err_catch(__err, __env, NULL); {

#define TRY \
  else { __catcherr = err_catch(__err, __env, NULL); {

#define ENDCATCH \
  }} err_leave(&__catcherr); }


PS2: Speaking about macro magic, I've also removed the STMT_START / STMT_END
macros from Ilya's mnemonic patch and made them available through the general
pari headers:

/* STMT_START { statements; } STMT_END;
 * can be used as a single statement, as in
 * if (x) STMT_START { ... } STMT_END; else ...
 * [ avoid "dangling else" problem in macros ] */
#define STMT_START      do
#define STMT_END        while (0)

-- 
Karim Belabas                    Tel: (+33) (0)1 69 15 57 48
Dép. de Mathematiques, Bat. 425  Fax: (+33) (0)1 69 15 60 19
Université Paris-Sud             Email: Karim.Belabas@math.u-psud.fr
F-91405 Orsay (France)           http://www.math.u-psud.fr/~belabas/
--
PARI/GP Home Page: http://www.parigp-home.de/