From a7bce1e8581c14c77c89d60149e7c70877faaf5f Mon Sep 17 00:00:00 2001
From: Phil Rosenberg <p.d.rosenberg@gmail.com>
Date: Sun, 16 Jul 2017 10:31:23 +0100
Subject: [PATCH 3/4] Added Exception handling macros/functions/variables

This commit simply adds the functions that would permit exception
handling. It does not start using it.

The handling is based around four macros PLTRY, PLCATCH and PLENDTRY,
plus PLTHROW.
They are used as follows
PLTRY(plsc)
{
    //some code to do stuff
}
PLCATCH
{
    //some code to deal with an error situation
}
PLENDTRY(plsc)

If at any point during "some code to do stuff" (including in
funtions called in here etc) PLTHROW is called then execution returns to
the PLCATCH code, the stack is restored to its state at PLTRY and any
memory allocation using the plmalloc family of functions is correctly
freed.

Reentrant behaviour is handled correctly so nested PLTRY and reentrant
API called (used extensively by plplot) are fine.

To impliment this exception style error handling every API function
should have its code wrapped in a PLTRY block.

The code works by calling setjmp in PLTRY. When called directly in this
manner this function always returns true and therefore the code in the
PLTRY block is executed. Before the setjmp call PLTRY sets up memory
handling by calling plinitialisememorylist and selects a new jmp_buf
buffer from a stack by calling advancejumpbuffer. The advancejumpbuffer
function will create or grow the stack if needed.
If at any point PLTHROW is called, then longjmp is called. This function
returns execution back to the setjmp function call in PLTRY and unwinds
the stack to that point. In this case setjmp returns false and the code
in the PLCATCH block is executed.
Whether or not PLTHROW is called, the code in PLENDTRY restores the
previous item in the jmp_buf stack and frees any memory that was
allocated using the plmalloc set of functions. This ensures that
reentrant behaviour is dealt with and that no memory leaks occur. If we
have not reached the bottom of thejmp_buf stack in PLENDTRY then we have
had reentrant behaviour. In most cases (see below) we want to keep
unwinding the stack until our first entry point, so we PLTHROW again
without freeing all the memory. We save memory freeing for the last
CATCH.

There is at least one case where simply continuing to unwind the stack
is bad. We cannot longjmp over C++ code, e.g. in the drivers. One would
imagine that in this case no C++ destructors that should have been
called, would not be called. But officially the result is undefined
behaviour, which is obviously very bad! The Exception handling macros
may be added to or modified in the future so that when we have a driver
call we do something a bit different rather than just keep PLTHROWing to
the top API call. We may need some form of driver API, which to be
honest probably wouldn't be a bad thing.

Note that the do while(0) in the macros is just there to limit scope.
---
 include/plplotP.h | 30 ++++++++++++++++++++++++++++++
 include/plstrm.h  | 12 +++++++++---
 src/plcore.c      | 25 +++++++++++++++++++++++++
 3 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/include/plplotP.h b/include/plplotP.h
index c07d185..3e04f3a 100644
--- a/include/plplotP.h
+++ b/include/plplotP.h
@@ -169,6 +169,36 @@ extern PLDLLIMPEXP_DATA( PLStream * ) plsc;
 #endif
 
 //--------------------------------------------------------------------------
+//                       Exception handling macros
+//--------------------------------------------------------------------------
+//#ifdef PLEXCEPTION
+#include <setjmp.h>
+#define PLTRY( pls )       do\
+                           {\
+                               advancejumpbuffer( pls );\
+                               plinitialisememorylist( pls );\
+                               if ( !setjmp( *( ( pls )->currentjumpbuffer ) ) )\
+                               {
+
+#define PLCATCH                }\
+                               else\
+                               {
+
+#define PLENDTRY( pls )        }\
+                               --((pls)->currentjumpbuffer);\
+                               if((pls)->currentjumpbuffer>=(pls)->jumpbuffers)\
+                                   PLTHROW(pls);\
+                               plfreeall( pls );\
+                           } while ( 0 );
+
+#define PLTHROW( pls )     longjmp( *( pls )->currentjumpbuffer, 1 )
+
+void
+advancejumpbuffer(PLStream *pls);
+//#endif
+
+
+//--------------------------------------------------------------------------
 //                       Memory Management Functions
 //--------------------------------------------------------------------------
 
diff --git a/include/plstrm.h b/include/plstrm.h
index 1064c91..6d557a7 100644
--- a/include/plstrm.h
+++ b/include/plstrm.h
@@ -29,6 +29,7 @@
 #include "disptab.h"
 #include "pldll.h"
 #include "qsastime.h"
+#include <setjmp.h>
 
 //--------------------------------------------------------------------------
 // Define the PLDev data structure.
@@ -803,9 +804,14 @@ typedef struct
 
 // Memory allocation records, to allow us to free any left over memory
 // before we return from an API call and avoid memory leaks
-    void **memorylist;
-    void **memorylisthead;
-    void **memorylistend;
+    void    **memorylist;
+    void    **memorylisthead;
+    void    **memorylistend;
+
+// Stack of jump stacks
+    jmp_buf *jumpbuffers;
+    jmp_buf *currentjumpbuffer;
+    size_t  maxjumpbuffers;
 } PLStream;
 
 //--------------------------------------------------------------------------
diff --git a/src/plcore.c b/src/plcore.c
index f65ba8f..d789802 100644
--- a/src/plcore.c
+++ b/src/plcore.c
@@ -219,6 +219,31 @@ plrealloc( PLStream *pls, void *mem, size_t size )
 }
 
 //--------------------------------------------------------------------------
+//advancejumpstack
+//
+//move up the stack by one, but do not assign the next item - this is done
+//in the PLTRY macro. Increase the size of the stack as needed
+//--------------------------------------------------------------------------
+void advancejumpbuffer( PLStream *pls )
+{
+    if ( pls->maxjumpbuffers == 0 )
+    {
+        pls->maxjumpbuffers    = 10;
+        pls->jumpbuffers       = malloc( pls->maxjumpbuffers * sizeof ( jmp_buf ) );
+        pls->currentjumpbuffer = pls->jumpbuffers;
+    }
+    else if ( ( pls->currentjumpbuffer - pls->jumpbuffers ) == ( pls->maxjumpbuffers - 1 ) )
+    {
+        size_t offset = pls->currentjumpbuffer - pls->jumpbuffers;
+        pls->maxjumpbuffers   *= 2;
+        pls->jumpbuffers       = realloc( pls->jumpbuffers, pls->maxjumpbuffers * sizeof ( jmp_buf ) );
+        pls->currentjumpbuffer = pls->jumpbuffers + offset;
+    }
+
+    ++pls->currentjumpbuffer;
+}
+
+//--------------------------------------------------------------------------
 // Driver Interface
 //
 // These routines are the low-level interface to the driver -- all calls to
-- 
2.8.3

