exception handling, design by contract - delphi

what is the best way to handle invalid functions calls with not specified arguments for functions which do not have User interface access
function safeSQRT ( x : Real) : real;
begin
/// check for valid params .....
if (x<0) then
begin
exit;
/// create a exception here ??
end;
end;

With this signature
function safeSQRT(x : Real): Real;
you have but two options to signal an error:
Raise an exception.
Return a sentinel value that indicates an error.
The third option is to change the signature
function safeSQRT(x : Real; out retval: Real): Integer;
where the function return value is now an error code.
If you return an error code you force the caller to explictly check every single return value of each call. That's a heavy burden. Think of the fun involved in calling even the most mundane Win32 API to picture the burden.
If you return a sentinel value, then the same burden is placed on each and every caller.
If you raise an exception then the caller's job is easier. The caller can usually ignore exceptions and let them bubble upwards and be handled be a central exception handler.
The flip side is when the error condition occurs commonly and needs immediate handling. In such scenarios raising an exception is usually the wrong choice. If the error must always be handled at the call site then using exceptions makes it likely that the caller will forget to handle it.

Related

Is it risky to use exceptions like this?

I have the following function
function calculate(s:string):integer;
begin
try
strtoint(s);
do_something;
except
result:=-1;
end;
end;
Is there any consequence of using exceptions like this?
If the intent is to check the validity of the input string then there are better methods than an exception handler. To convert from string to integer and detect invalid input use TryStrToInt:
var
value: Integer;
...
if TryStrToInt(s, Value) then
Result := ...
else
Result := -1;
This function returns True if the string can be converted to an integer, and if so that integer is returned via the second argument, an out parameter. Otherwise the function returns False. In your code you ignore the converted integer value but it is available if you need it.
This is to be preferred over an exception handler, in my view, because it is more explicit and concise. Not to mention avoiding the performance overhead of raising and catching an exception.
Of course, your code will detect failures other than an invalid string. One potential source for errors is your do_something procedure. If you really want to swallow any exceptions raised by that procedure then an exception handler is needed. However, I rather suspect that it is more likely that your original intent was to catch invalid string input only. In which case your code was incorrect.
Another source of errors is if s happens to be an invalid string variable. But if that happens then the entire basis of your program is pulled from under you and I personally don't think you should expect to handle such scenarios gracefully.
Part of the problem with advising you is that your code is probably not truly representative. For instance it does not appear to set the return value if the input is valid. And we don't know for sure what the intent of your exception handler is. I'm guessing that you mean to trap errors in the call to StrToInt but I cannot tell that for sure.
It very much depends on the specification of the Calculate method.
If the specification stipulates that -1 is returned in the event of any and all exceptions then this is fine and (formatting issues aside) does the job as concisely as possible.
If the contract is only that the method returns -1 for invalid (non-numeric) strings then there is a potential issue that DoSomething itself might raise other exceptions (including possibly unrelated conversion errors) which will then be incorrectly handled, to yield -1 from the Calculate method (making it impossible to distinguish between invalid values of s and other errors.
In the latter case it would be more correct to handle the specific contract with respect to the string parameter in a way that avoids relying on an exception (since an error in this case is not "exceptional" but a specific input case to be handled) and allow exceptions from DoSomething to be handled by the caller, if appropriate.
To test for a valid numeric string you can use TryStrToInt and only call DoSomething if s is determined to be a valid integer. Assuming that the result of the Calculate function is -1 for invalid inputs and the result of the DoSomething operation on the numeric value of s otherwise:
function calculate(s: String):integer;
begin
if NOT TryStrToInt(s, result) then
result := -1;
else
result := DoSomething(result);
end;
The use of result in this way is a matter of personal preference. Some would argue that you should not use result for working storage like this and that you should use a separate local variable to hold the integer value of s, named accordingly:
function calculate(s: String): Integer;
var
inValue: Integer;
begin
if NOT TryStrToInt(s, inValue) then
result := -1;
else
result := DoSomething(inValue);
end;
FWIW: I personally would favour the latter. :)
Why don't you simply go for something like this:
function calculate(s:string):integer;
begin
result:=-1;
strtoint(s);
do_something;
result:=.... <--- whatever value you want to return or maybe result:=do_something as Deltics shows
end;

Delphi INTERNET_OPTION_RECEIVE_TIMEOUT Pointer error

i implemented the bwlow function to set INTERNET_OPTION_RECEIVE_TIMEOUT prior submitting to receive omething to avoid the timeout... this is working pretty well.
This function is implemented in a second form, i call from my main form with a menu.
When i close the second form, everything is ok. But when i close the main form, i get an error: Exception. Invalid Pointer...
if i dont call the function, i dont get the error... am stuck... anybody there to help me ?
function SetTimeout(const HTTPReqResp: THTTPReqResp; Data: Pointer; NumSecs : integer) : boolean;
var
TimeOut: Integer;
begin
// Sets the receive timeout. i.e. how long to wait to 'receive' the response
TimeOut := (NumSecs * 1000);
try
InternetSetOption(Data, INTERNET_OPTION_RECEIVE_TIMEOUT, Pointer(#TimeOut), SizeOf(TimeOut));
InternetSetOption(Data, INTERNET_OPTION_SEND_TIMEOUT, Pointer(#TimeOut), SizeOf(TimeOut));
except on E:Exception do
raise Exception.Create(Format('Unhandled Exception:[%s] while setting timeout to [%d] - ',[E.ClassName, TimeOut, e.Message]));
end;
end;
procedure TFmTestv2.HTTPRIO1HTTPWebNode1BeforePost(
const HTTPReqResp: THTTPReqResp; Data: Pointer);
begin
SetTimeout(HTTPReqResp, Data, 5 * 60);
end;
like described in this post
How do set the timeout on a wcf service caller in Delphi?
best regards
Holger
The most plausible explanation for an error relating to the code shown is that you are passing an invalid HINTERNET parameter. That's the first parameter you pass to the API function. My guess is that it's just something other than an HINTERNET. The fact that it is typed Pointer is a concern. According to the documentation this parameter must be either a valid HINTERNET or NULL. Make sure that it is. I see no evidence at all that the value of the Data parameter of the HTTPRIO1HTTPWebNode1BeforePost event handler is an HINTERNET.
I also suggest that you check for errors correctly. Start by reading the documentation for each API call you make, and following the instructions for error checking. You certainly fail to check for errors in the code shown. You've copied somebody else's code that does it wrongly. That's a risk when you copy code. In this case the code you have copied ignores return values which are how errors are signaled, and instead traps exceptions that are never raised.

Why does GetLastError return 0 when it's called in a DLL library?

Assume I'm having a DLL library with this pseudo-code:
var
LastError: DWORD;
procedure DoSomethingWrong; stdcall;
var
FileStream: TFileStream;
begin
try
FileStream := TFileStream.Create('?', fmCreate);
except
on EFCreateError do
LastError := GetLastError; // <- why does GetLastError return 0 here ?
end;
end;
Why does GetLastError function return 0 when it's used in a DLL library like shown above ? Is there a way to get the last error code for this case ?
Your call to GetLastError returns 0 because there are other APIs called after CreateFile returns, and your exception code executes.
The error code returned by GetLastError a thread local variable and is shared between all code that runs in your thread. So in order to capture the error code you need to call GetLastError immediately after the failed function returns.
The documentation explains it like this:
Functions executed by the calling thread set this value by calling the
SetLastError function. You should call the GetLastError function
immediately when a function's return value indicates that such a call
will return useful data. That is because some functions call
SetLastError with a zero when they succeed, wiping out the error code
set by the most recently failed function.
If you are using TFileStream.Create then the framework doesn't give you an opportunity to call GetLastError at the suitable moment. If you really want to get that information you will have to call CreateFile yourself and use a THandleStream instead of TFileStream.
The idea there is that with THandleStream you are responsible for the synthesis of the file handle which you pass to the constructor of THandleStream. That gives you the opportunity to capture the error code in case of failure.
At a higher level, the real issue with this code is that it is mixing models. You're attempting to create or open a file with one system (VCL TStream system) but you're testing for errors produced by a different system (Win32 API).
The only way you can rely on the Win32 GetLastError result is if you are calling Win32 functions yourself. Why? Because that's the only way to ensure that there are no other calls to Win32 functions between your Win32 function call and your call to GetLastError. Every Win32 API call has the potential to (re)set GetLastError.
Even though VCL sits on top of Win32, there is plenty of opportunity for some other Win32 API call to occur between when the error occurs and when the exception reaches your handler. Even if things worked fine today, some future change in the VCL implementation could easily disrupt the happy coincidence that is the current situation.
The best way to avoid this "hang time" where the data you need is vulnerable to being overwritten is to have the GetLastError value captured as close to the point of failure as possible and incorporated into a property of the VCL exception object. This would all but eliminate the risk of some innocent interloper between your exception handler and the point of failure obliterating the GetLastError global state.

Is Result variable defined from first line in a function?

I need a clarification of this case.
According my tests the Result variable is defined to:
Boolean=False, Integer=0, String='', Object=nil etc from the first line.
But I have never seen an official reference for this.
It also make sense as this gives the hint.
[DCC Warning] Unit1.pas(35): H2077 Value assigned to 'TForm1.Test' never used
function TForm1.Test: Boolean;
begin
Result := False;
// Some arbitrary code here
Result := True;
end;
But what happens if I comment out the first line and there is an exception somewhere before last line? Is Result = False ?
If Result is undefined this means that I always have to start every function by defining Result in case of exception later. And this make no sense for me.
As stated by the official Delphi documentation, the result is either:
CPU register(s) (AL / AX / EAX / RAX / EAX:EDX) for ordinal values and elements contained in a register;
FPU register (st(0) / XMM1);
An additional variable passed as a latest parameter.
The general rule is that no result value is defined by default. You'll have to set it. The compiler will warn you about any missing result set.
For a string, dynamic array, method pointer, or variant result, the
effects are the same as if the function result were declared as an
additional var parameter following the declared parameters. In other
words, the caller passes an additional 32-bit pointer that points to a
variable in which to return the function result.
To be accurate, the var parameter is not only for managed types, but only for record or object results, which are allocated on the stack before calling, so are subject to the same behavior.
That is, for instance, if your result is a string, it will passed as an additional var parameter. So it will contain by default the value before the call. It will be '' at first, then if you call the function several times, it will contain the previous value.
function GetString: string;
// is compiled as procedure GetString(var result: string);
begin
if result='' then
result := 'test' else
writeln('result=',result);
end;
function GetRaise: string;
// is compiled as procedure GetRaise(var result: string);
begin
result := 'toto';
raise Exception.Create('Problem');
end;
var s: string;
begin
// here s=''
s := GetString; // called as GetString(s);
// here s='test'
s := GetString; // called as GetString(s);
// will write 'result=test' on the console
try
s := GetRaise; // called as GetRaise(s);
finally
// here s='toto'
end;
end;
So my advices are:
Fix all compiler warning about unset result;
Do not assume that a result string is initialized to '' (it may be at first, but not at 2nd call) - this is passed as a var parameter, not as a out parameter;
Any exception will be processed as usual, that is, the running flow will jump to the next finally or except block - but if you have a result transmitted as a var parameter, and something has been already assigned to result, the value will be set;
It is not because in most cases, an unset result ordinal value (e.g. a boolean) is 0 (because EAX=0 in asm code just before the return), that it will be next time (I've seen random issues on customer side because of such unset result variables: it works most time, then sometimes code fails...);
You can use the exit() syntax to return a value, on newer versions of Delphi.
You state:
If Result is undefined this means that I always have to start every function by defining Result in case of exception later.
You are concerned that the return value of a function is undefined if the function raises an exception. But that should not matter. Consider the following code:
x := fn();
If the body of the function fn raises an exception then, back at the call site, x should not be assigned to. Logically the one-liner above can be thought of as a two-liner:
call fn()
assign return value to x
If an exception is raised in line 1 then line 2 never happens and x should never be assigned to.
So, if an exception is raised before you have assigned to Result then that is simply not a problem because a function's return value should never be used if the function raises an exception.
What you should in fact be concerned about is a related issue. What if you assign to Result and then an exception is raised? Is it possible for the value you assigned to Result to propagate outside of the function? Sadly the answer is yes.
For many result types (e.g. Integer, Boolean etc.) the value you assign to Result does not propagate outside the function if that function raises an exception. So far, so good.
But for some result types (strings, dynamic arrays, interface references, variants etc.) there is an implementation detail that complicates matters. The return value is passed to the function as a var parameter. And it turns out that you can initialise the return value from outside the function. Like this:
s := 'my string';
s := fn();
When the body of fn begins execution, Result has the value 'my string'. It is as if fn is declared like this:
procedure fn(var Result: string);
And this means that you can assign to the Result variable and see modifications at the call site, even if your function subsequently raises an exception. There is no clean way to work around it. The best you can do is to assign to a local variable in the function and only assign Result as the final act of the function.
function fn: string;
var
s: string;
begin
s := ...
... blah blah, maybe raise exception
Result := s;
end;
The lack of a C style return statement is felt strongly here.
It is surprising hard to state accurately which type of result variables will be susceptible to the problem described above. Initially I thought that the problem just affected managed types. But Arnaud states in a comment that records and objects are affected too. Well, that is true if the record or object is stack allocated. If it is a global variable, or heap allocated (e.g. member of a class) then the compiler treats it differently. For heap allocated records, an implicit stack allocated variable is used to return the function result. Only when the function returns is this copied to the heap allocated variable. So the value to which you assign the function result variable at the call site affects the semantics of the function itself!
In my opinion this is all a very clear illustration of why it was a dreadful mistake, in the language design, for function return values to have var semantics as opposed to having out semantics.
No, Result has no (guaranteed) default value. It is undefined unless you give it a value. This is implied by the documentation, which states
If the function exits without assigning a value to Result or the
function name, then the function's return value is undefined.
I just tried
function test: integer;
begin
ShowMessage(IntToStr(result));
end;
and got a message with the text 35531136.

It is possible to resume execution from the point where an exception was raised?

There is an example which illustrates my question:
procedure Test;
begin
try
ShowMessage('1');
raise EWarning.Create(12345, 'Warning: something is happens!');
ShowMessage('2');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
E.SkipThisWarning // shows '1' then '2'
else
E.StopExecution; // shows '1'
end;
end;
function IWantToContinue(const ErrorCode: Integer): Boolean;
begin
//...
end;
I tried to use something like this:
asm
jmp ExceptAddr
end;
but it's wont' work...
Any ideas?
Thanks.
No, it is not possible:
There are two kinds of exceptions: logic exceptions that are raised by the programmer using the raise command, and external exceptions that are initiated by the CPU for various conditions: division by zero, stack overflow, access violation. For the first kind, the logic exceptions, there's nothing you can do because they're part of the application "flow". You can't mess with the flow of 3rd party code, you can't even mess with the flow of your own code.
External exceptions
Those are normally raised as a consequence of running a single CPU instruction, when that instruction fails. In Delphi those are made available as EExternal descendants. The list includes access violations, division by zero, stack overflow, privileged instruction and not-so-many others. Theoretically, for some of those exceptions, the causing condition of the exception could be removed and the single CPU instruction retried, allowing the original code to continue as if no error happened. For example SOME access violations might be "fixed" by actually mapping a page of RAM at the address where the error occurred.
There are provisions in the SEH (Structured Exception Handling) mechanism provided by Windows for dealing with such retry-able errors, and Delphi is using SEH under the hood. Unfortunately Delphi doesn't expose the required elements to make this easily accessible, so using them would be very difficult if not impossible. None the less, for particular types of EExternal errors, smart Delphinians might attempt writing custom SEH code and get things working. If that's the case, please ask a new question mentioning the particular type of error you're getting plus the steps you'd like to take to remove the error condition: you'll either get some working code or a customized explanation of why your idea would not work.
Logic exceptions initiated through the use of raise
Most exceptions fall into this category, because most code will check it's inputs before doing potentially dangerous low level stuff. For example, when trying to access an invalid index in a TList, the index would be checked and an invalid index exception raised before attempting to access the requested index. Without the check, accessing the invalid index would either return invalid data or raise an Access Violation. Both of those conditions would be very hard to track errors, so the invalid index exception is a very good thing. For the sake of this question, even if code were allowed to access an invalid index, causing an Access Violation, it would be impossible to "fix" the code and continue, because there's no way to guess what the correct index should be.
In other words, fixing "logic" exceptions doesn't work, shouldn't work, and it's insanely dangerous. If the code that raises the error is yours then you can simply restructure it to NOT raise exceptions for warnings. If that's not your code, then continuing the exception falls into the "insanely dangerous" category (not to mention it's technically not possible). When looking at already written code, ask yourself: would the code behave properly if the raise Exeption were replaced with ShowMessage? The answer should mostly be "NO, the code would fail anyway". For the very rare, very wrong case of 3rd party code that raises an exception for no good reason, you may ask for specific help on patching the code at run-time to NEVER raise the exception.
Here's what could be in some 3rd party code:
function ThirdPartyCode(A, B: Integer): Integer;
begin
if B = 0 then raise Exception.Create('Division by zero is not possible, you called ThirdPartyCode with B=0!');
Result := A div B;
end;
It should be obvious that continuing that code after the exception is not going to allow stuff to "self heal".
Third party code might also look like this:
procedure DoSomeStuff;
begin
if SomeCondition then
begin
// do useful stuff
end
else
raise Exception.Create('Ooops.');
end;
Where would that code "continue"? Quite obviously not the "do usefull stuff" part, unless the code is specifically designed that way.
Those were, of course, simple examples only scratching the surface. From a technical perspective, "continuing" after an exception as you're suggesting is a lot more difficult then jumping to the address of the error. Method calls use stack space to set up local variables. That space was released in the process of "rolling back" after the error, on the way to your exception handler. finally blocks were executed, possibly de-allocating resources where needed. Jumping back to the originating address would be very wrong, because the calling code no longer has what it expects on stack, it's local variables are no longer what they're ment to be.
If it's your code that's raising the exception
Your code can easily be fixed. Use something like this:
procedure Warning(const ErrorText:string);
begin
if not UserWantsToContinue(ErrorText) then
raise Exception.Create(ErrorText);
end;
// in your raising code, replace:
raise Exception.Create('Some Text');
// with:
Warning('Some Text');
AFAIK no. You have to restructure your code to something like
procedure Test;
begin
ShowMessage('1');
try
raise EWarning.Create(12345, 'Warning: something is happens!');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
// shows '1' then '2'
else
raise; // shows '1'
end;
ShowMessage('2');
end;
In C++, it is possible using an SEH __try/__except block whose __except expression evaluates to EXCEPTION_CONTINUE_EXECUTION.
In Delphi, it is not possible to use SEH directly, AFAIK.
BASICally, your code wont work because ExceptAddr is a function, not a variable. So, your code snippet changes as follows:
{$O-}
procedure TForm1.FormCreate(Sender: TObject);
begin
try
OutputDebugString('entering');
raise Exception.Create('Error Message');
OutputDebugString('leaving');
except on E: Exception do
begin
OutputDebugString(PChar(Format('ExceptAddr = %p', [ExceptAddr])));
asm
CALL ExceptAddr
JMP EAX
end;
end;
end;
end;
..you could nest multiple try-except and determine if continue inside each exception:
try
try
some code
except
ret := message('want to continue?');
if ret <> mrYes then
exit;
end;
some other code to perform when no exception or user choose to continue
except
etc..
end;

Resources