try...except

No matter what.

try // here goes some horrible or not so horrible code // that can raise the most horrible exceptions except // no matter how horrible exceptions are // you will always land here where you can handle them, // eat them up and pretend they never happened, // or do something and re-raise them // YOU ARE IN CONTROL end;

type TFoo = class(TObject) public procedure Foo; virtual; end; procedure TFoo.Foo; begin end; var Foo: TFoo; begin Foo := nil; try // calling a virtual method on nil reference raises an Access Violation exception Foo.Foo; except Log.d('CAUGHT'); end; end;

try...except

Wait.... WHAT.... What a bug!!!?

try...except

try...except

Not so fast... There are some exceptions...

try...except

nil

nil

type TFoo = class(TObject) public procedure Foo; end;

nil

try...except

nil

nil

nil

try...except

var Foo: TFoo; begin try if Assigned(Foo) then Foo.Foo else raise Exception.Create('Foo is nil'); except Log.d('CAUGHT'); end; end;

To be continued... All Hell Breaks Loose

try...finally and implicit method finalization blocks are also broken...

It is common knowledge that exceptions raised in some piece of Delphi code can be caught and handled withblocks.For example:Capturing and handling exceptions withinblocks is one of the cornerstones of Delphi coding practices. Millions of lines of code depend on that exception trap, from which there is no escape unless you explicitly let it out by re-raising.So you think you have all possible exceptions neatly wrapped up and appropriately handled.Not really... there is this teeny-weeny thingy... your exception-catching stronghold is not so strong on Delphi compilers with the LLVM backend.Running the above code on Android, iOS or Linux will not catch the exception as expected, and it will bubble up to the next exception handler level. Your perfect application suddenly starts misbehaving.Actually, it is not a bug, it is a feature. Well, not an actual feature... but rather a documented behavior. But who reads the documentation, right?Even if you do read it, like I did (more than once), you might have been focused on other mobile compiler features, like ARC or the lack of 8-bit strings. And that exception thingy can easily go unnoticed. I certainly managed to miss it.To make a long story short, the LLVM backend cannot return from a hardware exception (like AV) if the hardware exception is raised directly within ablock. It can only safely return if there is a function (method) call within theblock.To work around the escaping exception problem, all you have to do is wrap your code in functions (methods) and you are back in business.What may seem like a simple function call to us, may not be a simple function call to the compiler...If a function or method is inlined, then there is no actual function - all code will be inserted in place - and there will be no function call to return from.Even if not inlined, not all method calls are simple function calls behind the scenes. If the exception is raised in code before the actual function call is made, the exception will not be caught by the immediate exception handler.And that is what happened in our example. There is a method call inside theblock. According to the function rule, that exception should have been caught. But, calling a virtual method goes through the object instance's Virtual Method Table (VMT), and if it runs into areference, that process itself triggers an exception. You cannot find the right method to call if the place where you are looking for it does not exist.Changing the method signature from virtual to static, changes the outcome - static methods can be called onreferences. Even if the code within the static method raises an exception, that exception will be successfully caught because it happened inside the function, not during the call.When it comes to interface references, all method calls are virtual. Calling any method (even if it is implemented as static) on ainterface reference will throw an exception during the call and such an exception will not be caught by ablock.So, you have two options when it comes to capturingexceptions inside a particular exception handling block - you either have to wrap your code in functions (methods) (following all of the above mentioned function rules), or you have to prevent a hardware exception from happening in the first place.In the case of virtual calls on areference, that would consist of checking forbefore making the call. If you like (or need), if you find areference you can safely raise a Delphi exception (software exception), as this one can be properly captured by theblock on all platforms.