Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 454
Victor Seregin Avatar asked Feb 08 '12 10:02

Victor Seregin


1 Answers

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');
like image 169
Cosmin Prund Avatar answered Nov 10 '22 01:11

Cosmin Prund