11 May, 2022

Why it is recommended to restart the application after a crash

We were contacted by a client complaining that EurekaLog was hiding his application.

In particular, the client was trying to make the error dialog to appear only for certain exceptions. He tried to do this with the following code:
if AShowDlg then
  EI.Options.ExceptionDialogType := edtEurekaLog
else
  EI.Options.ExceptionDialogType := edtNone;
and was testing it like this:
procedure TForm1.Button1Click(Sender: TObject);
var
  List: TStringList;
begin
  try
    List.Add('Test');
  except
    on E: Exception do
      HandleError(E);
  end;
end;
where HandleError is the client's code for handling exceptions.

At the same time, the client claimed that "everything works correctly without EurekaLog", and "with EurekaLog: after processing the test exception, (another) Access Violation is raised, and the application disappears from the monitor, while still showing in the task manager".

Do you see a problem in the above code?

The problem is that the test case uses an uninitialized List variable. This means that it can be any value (which will be an arbitrary garbage from the stack left after execution of the previous code).

It just so happened (read: "lucky") that in an application without EurekaLog, this garbage pointed to an invalid memory area, so an attempt to call the List.Add threw an Access Violation exception immediately when the code tried to call the method. The message looked like this: 'Access violation at address 00000001 in module 'Project1.exe'. Read of address 00000001'. Note that both addresses match and are invalid.

However, when EurekaLog was added to the application, the situation changed, and a different garbage value appeared on the stack. It just so happened (read: "unlucky") that this garbage pointed to a memory area in which the address of another method was written. We don't know what this value was on the client's machine, but when we checked, the value in memory pointed to the TCustomForm.Create. Therefore, the List.Add call actually called the TCustomForm.Create - with a junk Self (pointing to the main form) and junk arguments. Of course, this cannot work correctly. The VCL code started executing, used garbage from the arguments, and eventually crashed when trying to register the created form with a non-existent Owner. The message looked like this: 'Access violation at address 004092F9 in module 'Project1.exe'. Read of address E8C78BD6'. Note that the addresses are different; and the first address is correct, but the second is not.

EurekaLog has catched this exception (which the client mistook for an exception when trying to call the List.Add) and handled it correctly. However, the VCL was already in a corrupted state. Namely, when the EurekaLog dialog was shown (or after it was closed), the message loop accessed the saved garbage data, which led to the second ("incorrect") Access Violation exception (which EurekaLog also caught).

The application "disappeared" for the reason that the main form was "overwritten" when it was passed as Self in TCustomForm.Create.

That is why, if you want to test throwing exceptions, then you should do it like this:
List := nil; // - added
List.Add('Test');

This example also perfectly illustrates why you should restart or close your application immediately after handling an exception: your application may be in a invalid state. Indeed, if it were in the correct state, then the unexpected exception would not have occurred. And once something unexpected happened, the application is no longer in the expected state. This means that its further behavior is undefined. An attempt to continue execution can lead to data corruption, the throwing of other (extremely difficult to diagnose) exceptions, and so on.

Of course, it is extremely important to separate really "unexpected" exceptions from "expected" ones. Unfortunately, this is a vast topic that is beyond the scope of this post.

Read more stories like this one or read feedback from our customers.