[Hugs] #84: Unnecessary "Control stack overflow"

Hugs trac at galois.com
Thu Oct 2 05:15:24 EDT 2008


#84: Unnecessary "Control stack overflow"
--------------------------------+-------------------------------------------
 Reporter:  guest               |       Owner:  nobody
     Type:  defect              |      Status:  new   
 Priority:  major               |   Milestone:        
Component:  hugs                |     Version:  200609
 Keywords:  Exception handling  |  
--------------------------------+-------------------------------------------
 I am not terribly familiar with the Hugs source code, but I believe I have
 found a bug relating to the interaction between the global variable
 `evalDepth` in `machine.c` and the implementation of exception handling.

 It seems that `evalDepth` is used to keep track of the recursion depth of
 the `eval` function; the first thing `eval` does is increment `evalDepth`
 and the last thing it does is decrement it.  If `evalDepth` ever gets too
 large, Hugs generates a "`Control stack overflow`".

 Now consider the function `evalWithNoError` (defined in `machine.c`),
 which is called by the primitive `primCatchException` to evaluate an
 expression which might cause an exception.

 {{{
 Cell evalWithNoError(e)                /* Evaluate expression, returning
 */
 Cell e; {                              /* NIL if successful, */
     Cell caughtEx;                     /* Exception value if not... */
     jmp_buf *oldCatch = evalError;

     jmp_buf catcherr[1];
     evalError = catcherr;
     if (setjmp(catcherr[0])==0) {
       eval(e);
       caughtEx = NIL;
     }
     else {
       caughtEx = exception;
     }

     evalError = oldCatch;
     return caughtEx;
 }
 }}}

 Notice that `setjmp` is called to save the state; if `eval` happens to
 invoke the primitive `throwException` (which calls longjmp) then the
 second branch of the of the conditional is taken, and an exception is
 returned.  The problem is that in such a situation, by calling longjmp,
 the `eval` function exits WITHOUT decrementing `evalDepth`. As far as I
 can tell, setjmp/longjmp does not save/restore `evalDepth`.  The result is
 that Hugs often generates "`Control stack overflow`" when evaluating
 expressions that throw and catch lots of exceptions.  I've attached an
 example of such a program -- this program causes no problems at all when
 run using GHC.

 By manually saving the `evalDepth` before calling `setjmp` and restoring
 it when `longjmp` is called, the problem can be fixed.  Here is the new
 definition of `evalWithNoError`.

 {{{
 Cell evalWithNoError(e)                /* Evaluate expression, returning
 */
 Cell e; {                              /* NIL if successful, */
     Cell caughtEx;                     /* Exception value if not... */
     jmp_buf *oldCatch = evalError;
     int oldEvalDepth;                  /* ADDED BY ME */
     oldEvalDepth = evalDepth;          /* ADDED BY ME */

     jmp_buf catcherr[1];
     evalError = catcherr;
     if (setjmp(catcherr[0])==0) {
       eval(e);
       caughtEx = NIL;
     }
     else {
       evalDepth = oldEvalDepth;        /* ADDED BY ME */
       caughtEx = exception;
     }

     evalError = oldCatch;
     return caughtEx;
 }
 }}}

 Of course, I do not know if this is a genuine fix or a broken hack. In
 particular, I worry about other side-effects of `eval` that are not undone
 when an exception is caught.  It is not obvious to me why setjmp and
 longjmp are required at all --- why not model an exception as a normal-
 form and simply inspect it after an `eval`?

 Is there a regression test-suite that I can try --- to gain confidence
 that the proposed fix does not break anything?

 Matthew Naylor (`mfn at cs.york.ac.uk`).

-- 
Ticket URL: <http://hackage.haskell.org/trac/hugs/ticket/84>
Hugs <http://www.haskell.org/hugs/>
Hugs 98, an interpreter for Haskell


More information about the Hugs-Bugs mailing list