I have this code (that runs under iOS with Delphi Tokyo):
procedure TMainForm.Button1Click(Sender: TObject);
var aData: NSData;
begin
try
try
aData := nil;
finally
// this line triggers an exception
aData.release;
end;
except
on E: Exception do begin
exit;
end;
end;
end;
Normally the exception should be caught in the except end
block, but in this case it is not caught by the handler and it is propagated to the Application.OnException
handler.
Access violation at address 0000000100EE9A8C, accessing address 0000000000000000
Did I miss something?
This is a bug (actually, a feature) on iOS and Android platforms (possibly on others with LLVM backend - though they are not explicitly documented).
Core issue is that exception caused by virtual method call on
nil
reference constitutes hardware exception that is not captured by nearest exception handler and it is propagated to the next exception handler (in this case to Application exception handler).Use a Function Call in a try-except Block to Prevent Uncaught Hardware Exceptions
The simplest code that exhibits the issue on iOS and Android platform is:
Executing above code on Windows platform works as expected and the exception is caught by exception handler. There is no
nil
assignment in above code, becauseaData
is interface reference and they are automatically nilled by compiler on all platforms. Addingnil
assignment is redundant and does not change the outcome.To show that exceptions are caused by virtual method calls
In all following code variants, exception escapes exception handler.
Even if we change
Foo
method implementation and remove all code from it, it will still cause escaping exception.If we change
Foo
declaration from virtual to static, exception caused by division to zero will be properly caught because call to static methods onnil
references is allowed and call itself does not throw any exceptions - thus constitutes function call mentioned in documentation.Another static method variant that also causes exception that is properly handled is declaring
x
asTFoo
class field and accessing that field inFoo
method.Back to the original question that involved
NSData
reference.NSData
is Objective-C class and those are represented as interfaces in Delphi.Since calling methods on interface reference is always virtual call that goes through VMT table, in this case behaves in similar manner (exhibits same issue) as virtual method call invoked directly on object reference. The call itself throws an exception and is not caught by nearest exception handler.
Workarounds:
One of the workarounds in code where reference might be
nil
is checking it fornil
before calling virtual method on it. If needed, in case ofnil
reference we can also raise regular exception that will be properly caught by enclosing exception handler.Another workaround as mentioned in documentation is to put code in additional function (method)