home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / System / ThreadLib 1.0d1 / Source / ExceptionLib / ExceptionLib.c next >
Encoding:
Text File  |  1994-02-10  |  9.7 KB  |  301 lines  |  [TEXT/KAHL]

  1. /* See the file Distribution for distribution terms.
  2.     (c) Copyright 1994 Ari Halberstadt */
  3.  
  4. /* Implements exception handling using ANSI C. The macro names were inspired
  5.     by the exception handling mechanism in THINK C, but the code is original.
  6.     Any number of nested exception handlers may be used within any function.
  7.     The basic syntax for exception handling is:
  8.     
  9.         TRY {
  10.             ... main body of routine ...
  11.         } CLEANUP {
  12.             ... code to execute on success and on failure ...
  13.         } CATCH {
  14.             ... code to execute only on failure ...
  15.         } ENDTRY;
  16.     
  17.     Exceptions are raised using the RAISE macro, though you usually will not
  18.     use the RAISE macro directly. Several functions are provided for raising
  19.     exceptions, such as FailOSErr which raises an exception when an operating
  20.     system error occurs, and FailNIL which raises an exception if a pointer
  21.     is NULL. You will usually rely on these FailXxxx functions to raise
  22.     exceptions.
  23.     
  24.     The CLEANUP handler is optional, while the TRY, CATCH, and ENDTRY
  25.     portions are required. If a CLEANUP handler is provided, it must
  26.     follow the TRY handler and precede the CATCH handler.
  27.     
  28.     When an exception is raised, execution jumps to the CLEANUP or CATCH
  29.     handler corresponding to the most recently executed TRY statement.
  30.     The code of the CLEANUP handler, if any, is executed first. Execution
  31.     then proceeds to the code of the CATCH handler. When the ENDTRY statement
  32.     is executed, another exception is raised, causing execution to proceed
  33.     with the next enclosing CLEANUP or CATCH handler. This process repeats
  34.     until a RETRY or NOPROPAGATE statement is executed. The application must
  35.     provide a RETRY or NOPROPAGATE statement in some top-level handler
  36.     (which generally will exit the application), or else the program will
  37.     crash when the stack of handlers has been emptied. If an exception is
  38.     raised while executing a CLEANUP or CATCH handler, then execution
  39.     proceeds with the next enclosing CLEANUP or CATCH handler.
  40.     
  41.     According to the documentation provided with THINK C, any local variable
  42.     whose value may have changed between a call to setjmp and the call to
  43.     longjmp must be declared volatile to ensure that it contains the correct
  44.     value. Since the TRY macro calls setjmp, and the RAISE macro calls longjmp,
  45.     any variables used within the CATCH or CLEANUP handlers, and which may
  46.     have changed value during execution of the function, must be either
  47.     of static or global scope, or they must be declared to be volatile.
  48.     
  49.     The TRY part of the exception handler can be retried using the RETRY
  50.     macro. For instance,
  51.     
  52.         long SomeFunction(...)
  53.         {
  54.             volatile Boolean failed = false;
  55.             long result;
  56.             
  57.             TRY {
  58.                 result = GetDefaultValue();
  59.                 if (! failed) {
  60.                     ... try algorithm ...
  61.                 }
  62.             } CATCH {
  63.                 if (! failed) {
  64.                     failed = true;
  65.                     RETRY;
  66.                 }
  67.             } ENDTRY;
  68.             return(result);
  69.         }
  70.         
  71.     There are several points worth noting about this example. First, we use
  72.     a variable 'failed' (declared volatile) to indicate that the initial
  73.     attempt failed and that we should therefore use a default value. Second,
  74.     the flag serves to prevent a possible infinite loop. For instance, if
  75.     the function GetDefaultValue() could itself fail, and if we didn't check
  76.     that we had already failed in the CATCH handler, 
  77.     
  78.         ...
  79.         } CATCH {
  80.            RETRY;
  81.         } ENDTRY;
  82.         ...
  83.         
  84.     then we would enter an infinite loop, since we would retry the operation,
  85.     which would call GetDefaultValue, which would fail, etc. Third, since
  86.     the 'result' variable is a local variable, we have to be careful
  87.     to reinitialize it inside the TRY handler.
  88.     
  89.     It is good programming practice to write functions so that they either
  90.     succeed or fail. It is much harder to validate software when functions
  91.     have the third alternative of neither succeeding nor failing, but of
  92.     just doing some undefined action. By definition, a function succeeds
  93.     when it fulfills its postcondition, and fails when it is doesn't
  94.     fulfill its postcondition (whether by the violation of an assertion,
  95.     the failure of some computer resource, invalid data, or due to any other
  96.     reason). A failed function results in a raised exception, which is
  97.     handled by the next CATCH handler. Following a failure, most functions
  98.     will do some minimal housekeeping, such as closing files they had opened,
  99.     and then simply allow the failure to propagate to the next CATCH handler. 
  100.     Some functions may attempt to execute with a different alogrithm, or may
  101.     return some default value, or may take any other action necessary to fulfill
  102.     their postcondition; these actions are best executed by setting a flag
  103.     to indicate failure and then using the RETRY macro.
  104.     
  105.     In some situations it may be necessary to use the NOPROPAGATE macro.
  106.     For instance, an Apple event handler function must return an error code
  107.     to the Apple Event Manager. This can be accomplished as follows:
  108.     
  109.         pascal OSErr HandleAEOpenApplication(AppleEvent *event,
  110.             AppleEvent *reply, long refcon)
  111.         {
  112.             OSErr err = noErr;
  113.             
  114.             TRY {
  115.                 AEGotRequiredParameters(event);
  116.                 // handle the event as appropriate for the application
  117.             } CATCH {
  118.                 err = FailReason();
  119.                 NOPROPAGATE;
  120.             } ENDTRY;
  121.             return(err);
  122.         }
  123.  
  124.     Since this function returns an error code, it still meets the criteria
  125.     of either succeeding or failing. We're just using the FailReason
  126.     function and the NOPROPAGATE macro to translate between an error handling
  127.     method based on exceptions an error handling method based on function
  128.     return values.
  129.     
  130.     It is helpful to users to provide additional information about the
  131.     operation being attempted when a failure occurred. This information
  132.     can then be displayed in an alert. Usually the top-level failure handler
  133.     in the application will do some house cleaning and then call FailDisplay.
  134.     FailDisplay uses the information set by the application to format a
  135.     string containing as much information as possible about the failure
  136.     and then displays it in an alert. After calling FailDisplay you should call
  137.     FailClear to reset the failure information so that the failure isn't
  138.     reported to the user more than once.
  139.     
  140.     The function FailInfoSet can be used by the application to describe
  141.     the operation that was attempted. For instance, when opening a file
  142.     you might use
  143.     
  144.         void OpenFile(FileType *fp)
  145.         {
  146.             FailInfoSet(RLS_ERR_READ, FileName(fp), NULL);
  147.             ... open file ...
  148.            FailInfoClear(); // clear info so next error doesn't display old info
  149.         }
  150.     
  151.     The parameters to FailInfoSet, along with the error code (if any) that
  152.     caused the failure, are passed to the function ErrorDisplay. See
  153.     ErrorDisplay (in ErrorLib.c) for a description of how error messages
  154.     are formatted and displayed. */
  155.  
  156. /*
  157.     94/02/10 aih - added ExceptionCopy to support thread context switches
  158.     94/02/08 aih - RAISE macro checks for null jmpenv to keep from crashing
  159.                         if there's no top-level NOPROPAGATE statement
  160.     94/01/28 aih - added big usage comment
  161.     94/01/19 aih - defined ExceptionTryType in ExceptionLib.h
  162.                      - added some comments to ExceptionLib.h
  163.     94/01/12 aih - added a few functions for setting information describing
  164.                         a failure
  165.     94/01/10 aih - made exception data a global variable
  166.     93/11/16 aih - fixed an error in the setup code which didn't properly
  167.                         reset gExecetion.jmpenv after a failure
  168.                      - added a comment describing a _try structure
  169.     93/10/26 aih - simplified macros a bit, fixed bug with prior bug fix...
  170.     93/10/23 aih - fixed bug with recursive failures/retries
  171.     93/10/19 aih - improved error reporting
  172.     93/03/?? aih - created */
  173.  
  174. #if WINTER_SHELL
  175.     #include "ErrorLib.h"
  176. #else /* WINTER_SHELL */
  177.     typedef char CStr255[sizeof(Str255)];
  178.     static void ErrorDisplay(OSErr err, short action, const CStr255 object,
  179.         const CStr255 explanation)
  180.     {
  181.         /* display an error message */
  182.     }
  183. #endif /* WINTER_SHELL */
  184.  
  185. #include <string.h>
  186. #include <PrintTraps.h>
  187. #include "ExceptionLib.h"
  188.  
  189. ExceptionType gException; /* information about current exception */
  190.  
  191. /* Copy the exception data from one structure to another. Since most of
  192.     the structure is just a a couple of big empty strings, this is more
  193.     efficient than doing a structure-to-structure assignment. This function
  194.     MUST NOT MOVE MEMORY. This function is primarily for use during
  195.     context switches by the thread library, you should never have to call
  196.     this function. */
  197. void ExceptionCopy(const ExceptionType *src, ExceptionType *dst)
  198. {
  199.     register const char *s;
  200.     register char *t;
  201.     
  202.     dst->jmpenv = src->jmpenv;
  203.     dst->err = src->err;
  204.     dst->action = src->action;
  205.     s = src->object;
  206.     t = dst->object;
  207.     while (*t++ = *s++)
  208.         ;
  209.     s = src->explanation;
  210.     t = dst->explanation;
  211.     while (*t++ = *s++)
  212.         ;
  213. }
  214.  
  215. void FailActionSet(short action)
  216. {
  217.     gException.action = action;
  218. }
  219.  
  220. void FailObjectSet(const CStr255 object)
  221. {
  222.     *gException.object = 0;
  223.     if (object)
  224.         strcpy((char *) gException.object, object);
  225.         
  226. }
  227.  
  228. void FailExplanationSet(const CStr255 explanation)
  229. {
  230.     *gException.explanation = 0;
  231.     if (explanation)
  232.         strcpy(gException.explanation, explanation);
  233. }
  234.  
  235. void FailInfoSet(short action, const CStr255 object, const CStr255 explanation)
  236. {
  237.     FailActionSet(action);
  238.     FailObjectSet(object);
  239.     FailExplanationSet(explanation);
  240. }
  241.  
  242. void FailInfoClear(void)
  243. {
  244.     FailInfoSet(0, NULL, NULL);
  245. }
  246.  
  247. void FailClear(void)
  248. {
  249.     gException.err = noErr;
  250.     FailInfoClear();
  251. }
  252.  
  253. void FailDisplay(void)
  254. {
  255.     if (gException.err != userCanceledErr)
  256.         ErrorDisplay(gException.err, gException.action,
  257.             gException.object, gException.explanation);
  258. }
  259.  
  260. OSErr FailReason(void)
  261. {
  262.     return(gException.err);
  263. }
  264.  
  265. void FailOSErr(OSErr err)
  266. {
  267.     if (err) {
  268.         if (err == iPrAbort)
  269.             err = userCanceledErr;
  270.         if (! gException.err)
  271.             gException.err = err;
  272.         RAISE;
  273.     }
  274. }
  275.  
  276. void FailMemError(void)
  277. {
  278.     FailOSErr(MemError());
  279. }
  280.  
  281. void FailResError(void)
  282. {
  283.     FailOSErr(ResError());
  284. }
  285.  
  286. void FailPrError(void)
  287. {
  288.     FailOSErr(PrError());
  289. }
  290.  
  291. void FailNIL(void *p)
  292. {
  293.     if (! p) FailOSErr(MemError() ? MemError() : memFullErr);
  294. }
  295.  
  296. void FailNILRes(void *p)
  297. {
  298.     if (! p) FailOSErr(ResError() ? ResError() : resNotFound);
  299. }
  300.  
  301.