Code archives/User Libs/Minimalist exceptions

This code has been declared by its author to be Public Domain code.

Download source code

Minimalist exceptions by Yasha2013
This code requires MikhailV's free FastPointer DLL.


This pair of rather unpleasant C functions lets you clunkily imitate exception handling in Blitz3D. For those who haven't used it before, exception handling lets you define control points with "handlers", and if you encounter an error deep inside many nested function calls, jump straight up the stack to the control point without having to mess about with returning error codes and have each function report that it failed to its individual call site and so on and so forth. Very good for the lazy coder.

In BlitzMax, you'd define the control point and handler with a Try/Catch block. We don't have those in Blitz3D, and have no way to define new syntax, so instead what we do is define the body and handler as two separate functions, and pass their function pointers to WithExceptionHandler (this is why you need the FastPointer DLL: this can't be done without function pointers), along with an argument for the body code.

Here's a demonstration:




In the event you hit an "exceptional situation", calling ThrowException will jump right out of whatever you were doing, all the way back to the level of WithExceptionHandler, and call the handler function with whatever you passed as the exception code, which can then take any necessary steps to rectify the situation using this code (use this to signal a particular flag or perhaps pass a data object). WithExceptionHandler will then return the result of the handler function instead of whatever would have been returned had no exception been thrown.

So in the above example:
-- WithExceptionHandler is called with Foo, Hdl and 47
-- WithExceptionHandler calls Foo with 47
-- Foo does some stuff and calls Bar with 42
-- Bar panics, as its mathematics breaks down around the non-value that is 42
-- Bar calls ThrowException with 42
-- ThrowException nukes the call stack all the way down to WithExceptionHandler again
-- WithExceptionHandler is informed that something went wrong, and calls Hdl to deal with it
-- Hdl deals with the problem, and returns a final value


It's not that difficult once you get used to it: it's basically just a way to immediately return past more than one function call at a time. Note that this doesn't catch or otherwise interact with system or hardware exceptions (division by zero, MAV, etc.) - it only works with your own "soft" errors.


Be warned however, that bypassing everything that happens in the rest of the function will also bypass B3D's own internal cleanup instructions! These are important for dealing with object reference counts, string memory, and other things that it manages to keep things safe. If you jump out of the middle of a function (or there's one in the middle of the nuked section of stack) that would have needed to cleanup strings or type objects, you will likely get memory leaks as the hidden cleanup code is also bypassed! Not to mention that of course any Free calls of your own will be skipped over too.

So, I suggest for safety:

-- preferably only use this code with functions that deal in integers and floats (you should be safe with entities, images, banks and other "int handle"-style objects)

-- if you have to use type objects, only pass them in parameter slots or via global variables. DO NOT use anything that would "return" an object (this includes the Object command itself) if there's a risk that the cleanup code will be skipped over. Simple parameters appear to be OK as B3D optimises out their refcounts anyway. If in doubt, do not use.

-- Do not use unboxed strings anywhere near this code. Literals should be OK, but I expect using string variables is asking for disaster. Pass them in globals or type objects if necessary.

-- finally, this code is not safe. If in doubt, do not use it at all! This is for advanced users who want to "play about" only! "Using setjmp for exception handling" is one of those things that almost everyone on the internet will tell you never to do, and with good reason.


Stern warnings aside... enjoy! The entry below contains both .decls and DLL (that's all! really! I can't believe it's that simple either); alternatively, download a prebuilt version.
;/* Exceptions.decls:

.lib "Exceptions.dll"

WithExceptionHandler%(proc%, handler%, arg%) : "WithExceptionHandler@12"
ThrowException(code%) : "ThrowException@4"

; */

#include <stdlib.h>
#include <setjmp.h>

#define BBCALL __attribute((stdcall))

static jmp_buf * topBuffer;
static int code;

BBCALL int WithExceptionHandler(int (* proc)(int) BBCALL,
                         int (* handler)(int) BBCALL,
                         int arg) {
	jmp_buf env;
	jmp_buf * prev;
	prev = topBuffer;
	topBuffer = &env;
	int res;
	if (!setjmp(env)) {
		res = proc(arg);
		topBuffer = prev;
	} else {
		topBuffer = prev;	// Can't return here on further problems
		res = handler(code);
	}
	return res;
}

BBCALL void ThrowException(int c) {
	code = c;
	longjmp(*topBuffer, 1);
}

Comments

virtlands2013
Fascinating. I shall look into this further.
As it seems to be very complex from a first glance, then first I shall not use it casually.

You said, "doesn't catch division by zero". Is there anything else you're not telling us.

Great job, by the way,...


Yasha2013
The reason it can't catch "hard" errors like division by zero or an illegal memory instruction is that there's already one exception system in place, provided by the OS: when the processor or MMU or whatever encounters something it considers illegal, it invokes that exception system; since B3D doesn't include an exception system of its own, the only handler for these is the one that the OS wrapped around the whole program on program startup (this is overly simplified). So the only control point it can see is the one wrapping the entire program structure.

Languages designed with exceptions from the start can be designed to make the compiler "aware" of OS exceptions (C++ can be designed to do this; BlitzMax does not as far as I can tell). But because this is a low-level system feature involving processor interrupts and the like, there's no easy way to write code that can integrate with it without the compiler having been designed to do it from the start.

This code provides a second exception system internal to your program logic. It is not tied to the hardware, and that means it can only catch exceptions that you threw by hand using ThrowException. The OS doesn't know about it and can't hook it's own existing exception system into it.

You could use it to deal with things like that, but only by writing wrapper code that detects the exceptional situation using the same kind of guards that you'd use to detect them in exceptionless B3D code:

Const DIV_BY_ZERO_CODE = ...

Function SafeDivide(l, r)
    If r = 0 Then ThrowException DIV_BY_ZERO_CODE
    Return l / r
End Function

Function MathStuff(a, b, c)
    ;Math
    ;Math
    Local d = SafeDivide(a, b)
    ;Math
    Return ...
End Function

Function Handler(code)
    Select code
        Case DIV_BY_ZERO_CODE
            Print "I'm sorry, I can't divide by zero Dave"
    End Select
    Return 0
End Function

Function DoAllTheMath(arg)
    ;...
    Return MathStuff(42, 0, 47)    ;This 0 is going to be a problem later
End Function

Local MathAllPtr = ..., HdlPtr = ...  ; ... get fn pointers

Local result = WithExceptionHandler(MathAllPtr, HdlPtr, something)


The "hard" error is checked the same way it normally would be, but it saves you having to do this:

Global ErrorStatusMonitor

Function SafeDivide(l, r)
    If r = 0 Then ErrorStatusMonitor = True : Return 0
    Return l / r
End Function

Function MathStuff(a, b, c)
    ;Math
    If Not ErrorStatusMonitor
         Local d = SafeDivide(a, b)
        If Not ErrorStatusMonitor
            ;Math
            Return ...
        EndIf
    EndIf
    Return 0
End Function

Local res = MathStuff(42, 0, 47)
If ErrorStatusMonitor Then Print "res is 0, but not a valid result, because something went wrong deep in the code"


(Extreme and ugly example.)

Anything you can do with exceptions, you can also do with error codes. Exceptions are just a shortcut to allow the code to more closely reflect your intent by removing the safety clutter that's necessary, but not related to the task at hand. Similar to garbage collection in that respect.


virtlands2013
Here's a simple + very safe (experimental) program I made to detect some math overflows and underflows.

Download link: http://uploadingit.com/file/ygba9xfllnxtkthe/SafeMath32_By_Virt.zip


It involves melding PureBasic, Blitz3d, and some assembly language together. As usual, B3D accesses some special functions through a DLL. Only those DLL functions supplied can detect overflows & underflows, so it's not 'exception' code in the same sense as yours.

An underflow occurs when a math calculation becomes so low that it can't be stored in a regular 32-bit variable, and it tends towards zero.
A gradual underflow occurs when any calculation loses precision by losing decimal point digits.
( Gradual underflows are too laborious to keep track off, so this program only tries with simple underflows. )
http://en.wikipedia.org/wiki/Arithmetic_underflow

It's too bad that we can't put assembly language directly into Blitz3D. Or can we?

{ a multiplication that's too large can cause a 'carry' }


{ an addition that's too large can cause an 'overflow' }

;----------------------------------------------------------


It has the usual logic, as in 1/0 = infinity.

I don't know how to pass or return TYPES into a DLL, (I sure tried though).
I had to do this weird thing where I cram 3 variables into one, and return the result that way.
Each variable takes 10 bits, for a total of 30 bits, which fits just fine in an INT.

Oh, the things I've got to do to make things work.


Yasha2013
It's too bad that we can't put assembly language directly into Blitz3D. Or can we?


TCC permits inline assembly within C functions.

Not quite the same as your PureBasic use case: the asm has to be in a string, and then put inside a C function to be compiled at runtime, but it will work if you're desperate.


I don't know how to pass or return TYPES into a DLL, (I sure tried though).


You can pass objects to the DLL using a .decls declaration like this:

SomeDLLFunction(obj*) : "_myFunc@4"


This will pass the DLL function a pointer to the first field. You can treat it as a C struct pointer from here on (I don't know PureBasic and can't tell you what the equivalent there would be). You use the same star sigil for all types, meaning one DLL function can accept any type of objects (also banks: note that it can't tell the difference between a valid bank and an arbitrary integer though, except when the latter crashes).

There is no facility for returning objects. While I can think of a few hacks, I don't think there is any safe way to do this actually. Consider storing and returning Handles instead.


virtlands2013
Okay, I shall study DLL parameters some more. Shall try to get it right this time.
What does the @4 mean? ( as in func1@4 )


Yasha2013
The string in the quote marks is the function's name as exported by the DLL. You can apparently leave this off if it's identical to the name you're giving it in B3D on the left.

The @X is commonly added by GCC and similar compilers to represent the fact that the function takes four bytes' worth of arguments on the stack. It's just a naming convention that takes advantage of the fact that DLLs can export names with characters that would be illegal in C code (so you can use to @ to separate off a part of the name with more information). It does not have any practical implications and is not "checked" at runtime or anything like that.

Besides, in this case I just made the name up.


virtlands2013
Aha! I've figured out TYPE parameters with simple INT fields, however the one's with string fields don't work.

This old topic by Oldefoxx may do the trick: I shall study this one.
String Pointers (Including NULL) For DLLs
http://www.blitzbasic.com/Community/post.php?topic=33007&post=354882
;------------------------------------------------------------------------------

I guess I should have started a new topic on this.

Displayed here is a successful attempt at accessing B3D types in a PureBasic DLL:



Basically, the structure in PureBasic is a duplication of the TYPE in Blitz3D.

func2 achieves the same result as func1, by using a kind of 'casting' of an INT to a structure pointer.

The corresponding DECLS file is very simple as follows:

.lib "DLL1.DLL"
func1%(a*)
func2%(a*)
func3%(a*)

;------------------------------------------
As for using string parameters, that's more complex, will be back...


Yasha2013
Note that the TypePointer function is a FastPointer extension, not a part of B3D.

Strings.... strings in B3D are "complicated" and I would advise not tampering with them too much as you might break some of its essential background features.

I was able to discover some of the lowlevel information about them a while back though. This program demonstrates:



i.e. Strings are a 24-byte (so six word) structure. The second word is a pointer to the actual string data buffer. The third and fourth words are the length of the string, and the length of the data buffer (allocated in blocks and therefore usually slightly longer than the string itself) respectively. The fifth and sixth words are pointers to the preceding and following strings in B3D's internal string management system. I was not able to work out what, if anything, the first data word represents, and I can't think of a use for it either (it doesn't appear to be a reference count).

Further thoughts:

-- the string headers are allocated from a free list to keep 'em small and fast
-- string buffer data looks to be handled similarly
-- every time a string is assigned to a variable, passed as a function parameter, or returned from a function, a new header is allocated which makes passing strings rather slow (stick them in an array or object for faster passing)
-- I was not able to work out/cannot remember how string buffers are shared, when B3D decides to copy them, or when it decides to free them. There is no obvious sign that it uses refcounting, but it might be using tracing instead which we can't easily tell from looking at memory dumps

Perhaps this helps (incidentally, if you want to investigate this further I suggest opening a forum thread, as we're no longer talking at all about the archive entry).


EDIT: if you meant string parameters to DLL functions - those are copied into a temporary buffer before the DLL is called; B3D's internal string data never gets passed to the DLL. I take advantage of this fact in my string to bank trick.


virtlands2013
Thanks Yasha, that's awesome. It's going to take me over a day to absorb and study all that.




Code Archives Forum