Difference between revisions of "Exceptions"

From Lazarus wiki
m (Performance)
 
(5 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Free Pascal supports exceptions. Exceptions are useful for error handling and avoiding resource leaks. However, bear in mind that exceptions have a performance impact.
+
{{Exceptions}}
  
The official documentation is here: [https://www.freepascal.org/docs-html/ref/refch17.html].
+
[[FPC|Free Pascal]] supports exceptions. Exceptions are useful for error handling and avoiding resource leaks. However, bear in mind that exceptions have a performance impact.
  
By default exceptions are disabled. You can opt in by using the OBJFPC or DELPHI [[Compiler Mode]], or adding -Sx to the compiler commandline. This enables the <code>try</code>, <code>raise</code>, <code>except</code>, and <code>finally</code> keywords for use in your own code, but it doesn't enable exception generation from the RTL. To enable the RTL to raise exceptions instead of generating runtime errors, use the SysUtils unit in your program.
+
The official documentation is here: [https://www.freepascal.org/docs-html/ref/refch17.html Reference guide chapter 17].
  
The base <code>Exception</code> class in SysUtils: [https://www.freepascal.org/docs-html/rtl/sysutils/exception.html]. All exceptions in code should preferably use this class or its descendants.
+
By default exceptions are disabled. You can opt in by using the [[Mode_ObjFPC|ObjFPC]] or [[Mode_Delphi|DELPHI]] [[Compiler Mode]], or adding -Sx to the [[Compiler|compiler]] [[Command-line_interface|commandline]]. This enables the [[Try|<syntaxhighlight lang="pascal" enclose="none">try</syntaxhighlight>]], [[Raise|<syntaxhighlight lang="pascal" enclose="none">raise</syntaxhighlight>]], [[Except|<syntaxhighlight lang="pascal" enclose="none">except</syntaxhighlight>]], and [[Finally|<syntaxhighlight lang="pascal" enclose="none">finally</syntaxhighlight>]] [[Reserved word|reserved words]] for use in your own code, but it doesn't enable exception generation from the [[RTL]]. To enable the RTL to raise exceptions instead of generating [[runtime error]]s, use the SysUtils unit in your [[Program|program]].
 +
 
 +
The base <syntaxhighlight lang="pascal" enclose="none">Exception</syntaxhighlight> [[Class|class]] is found in the [https://www.freepascal.org/docs-html/rtl/sysutils/exception.html SysUtils] unit. All exceptions should preferably use this class or its descendants.
 +
 
 +
SysUtils automatically sets up a basic exception catcher, so any otherwise uncaught exception is displayed in human-readable form, as the program terminates. To generate readable callstacks from caught exceptions in your own code, without necessarily terminating the program, you can use SysUtils functions <syntaxhighlight lang="pascal" enclose="none">ExceptAddr</syntaxhighlight>, <syntaxhighlight lang="pascal" enclose="none">ExceptFrames</syntaxhighlight>, <syntaxhighlight lang="pascal" enclose="none">ExceptFrameCount</syntaxhighlight>, and <syntaxhighlight lang="pascal" enclose="none">BackTraceStrFunc</syntaxhighlight>.
  
  
Line 11: Line 15:
  
  
Note that Pascal uses different keywords than some other languages: <code>raise</code> instead of <code>throw</code>, and <code>except</code> instead of <code>catch</code>. Also, as a compiler design choice, each <code>try</code>-block can pair with either an <code>except</code> or <code>finally</code> block, but not both at the same time. You'll need to nest <code>try</code>-blocks if you need <code>except</code> and <code>finally</code> protecting the same code.
+
Note that Pascal uses different [[Keyword|keywords]] than some other languages: <syntaxhighlight lang="pascal" enclose="none">raise</syntaxhighlight> instead of <syntaxhighlight lang="pascal" enclose="none">throw</syntaxhighlight>, and <syntaxhighlight lang="pascal" enclose="none">except</syntaxhighlight> instead of <syntaxhighlight lang="pascal" enclose="none">catch</syntaxhighlight>. Also, as a compiler design choice, each <syntaxhighlight lang="pascal" enclose="none">try</syntaxhighlight>-block can pair with either an <syntaxhighlight lang="pascal" enclose="none">except</syntaxhighlight> or <syntaxhighlight lang="pascal" enclose="none">finally</syntaxhighlight> block, but not both at the same time. You'll need to nest <syntaxhighlight lang="pascal" enclose="none">try</syntaxhighlight>-blocks if you need <syntaxhighlight lang="pascal" enclose="none">except</syntaxhighlight> and <syntaxhighlight lang="pascal" enclose="none">finally</syntaxhighlight> protecting the same code.
  
 
=== Error handling ===
 
=== Error handling ===
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
try
+
uses sysutils;
  // Do something that might go wrong.
+
 
except
+
begin
  on E : Exception do begin
+
  try
    // Try to recover or show the user an error message.
+
    // Do something that might go wrong.
 +
  except
 +
    begin
 +
      // Try to recover or show the user an error message.
 +
    end;
 
   end;
 
   end;
end;
+
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
=== Cleaning up resources ===
 
=== Cleaning up resources ===
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
try
 
try
 
   // Do something that might go wrong.
 
   // Do something that might go wrong.
 
finally
 
finally
   // Clean up code that is always called even if an exception was raised.
+
   // Clean-up code that is always called even if an exception was raised.
 
end;
 
end;
 +
</syntaxhighlight>
 +
 +
=== Exception leaking ===
 +
 +
Finally-blocks don't destroy exception objects. Any exceptions that reach the program's "end." will trigger a memory leak warning. An extra except block can be used to consume such exceptions. This is for [https://bugs.freepascal.org/view.php?id=33650 Delphi-compatibility].
 +
 +
<syntaxhighlight lang="pascal">
 +
begin
 +
  try
 +
    // Your main program, where an exception object is created.
 +
  finally
 +
    try
 +
      // Clean-up code.
 +
    except
 +
    end;
 +
  end;
 +
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
=== Signalling problems ===
 
=== Signalling problems ===
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
raise Exception.Create('Helpful description of what went wrong');
 
raise Exception.Create('Helpful description of what went wrong');
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 43: Line 68:
 
=== Using specialised exception types to signal different problems ===
 
=== Using specialised exception types to signal different problems ===
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
type EMyLittleException = Class(Exception);
 
type EMyLittleException = Class(Exception);
  
Line 52: Line 77:
 
     on E : EMyLittleException do writeln(E.Message);
 
     on E : EMyLittleException do writeln(E.Message);
 
     on E : Exception do writeln('This is not my exception!');
 
     on E : Exception do writeln('This is not my exception!');
 +
    else writeln('This is not an Exception-descendant at all!');
 
   end;
 
   end;
 +
end;
 +
</syntaxhighlight>
 +
 +
=== Re-raising exceptions ===
 +
 +
<syntaxhighlight lang="pascal">
 +
try
 +
  // Do something that might go wrong.
 +
except
 +
  // Try to recover or show the user an error message.
 +
  if recoveredSuccessfully = FALSE then
 +
    raise;
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 60: Line 98:
  
  
SysUtils defines and raises many specific exception classes: [https://www.freepascal.org/docs-html/rtl/sysutils/index-4.html]
+
SysUtils defines and raises many specific [https://www.freepascal.org/docs-html/rtl/sysutils/index-4.html exception classes].
 +
 
 +
With SysUtils included in your program, and exceptions enabled, various [https://www.freepascal.org/docs-html/user/userap4.html runtime errors] are changed into exceptions. Processor-level interrupts like SIGSEGV or SIGFPU, which normally translate to run-time errors, are also changed to exceptions. For example, run-time error 200 (division by zero) becomes EDivByZero or EZeroDivide, while run-time error 216 (a general protection fault) becomes EAccessViolation.
  
  
Line 66: Line 106:
  
  
As exceptions impose a performance penalty, some general guidelines apply:
+
* Raise exceptions to signal that an operation could not be completed, where it normally should have succeeded.
* Raise exceptions to signal that an operation that should have succeeded could not be completed.
+
* Do not use exceptions as part of expected control flow. Instead, add checks for common error conditions and return error values the old-fashioned way. For example, input parsing problems or file existence fails are usually not truly exceptional.
* Do not use exceptions as part of expected control flow. Instead, add checks for common error conditions and return error values the old-fashioned way.
 
 
* But do raise an exception if it's critical that the error is noticed; programmers may forget to check for returned error values.
 
* But do raise an exception if it's critical that the error is noticed; programmers may forget to check for returned error values.
 
* Naming convention: Prefix exception class names with a capital E.
 
* Naming convention: Prefix exception class names with a capital E.
* Re-raise exceptions in <code>except</code>-blocks using <code>raise;</code> if you were unable to recover from the problem; this preserves the original exception's callstack.
+
* Re-raise exceptions in <syntaxhighlight lang="pascal" enclose="none">except</syntaxhighlight>-blocks using <syntaxhighlight lang="pascal" enclose="none">raise;</syntaxhighlight> if you were unable to recover from the problem; this preserves the original exception's callstack.
* Be careful about using the catch-all base <code>Exception</code> class, since the underlying code or OS/filesystem might produce an unanticipated exception that slips through your exception handler, potentially corrupting the program state.
+
* Be careful about using the catch-all base <syntaxhighlight lang="pascal" enclose="none">Exception</syntaxhighlight> class, since the underlying code or OS/filesystem might produce an unanticipated exception that slips through your exception handler, potentially corrupting the program state.
 +
* When writing a unit or library, if exceptions are used at all, they should be documented clearly up front. This way the unit user knows what to expect.
 
* Keep exception handling away from code that needs to run as fast as possible.
 
* Keep exception handling away from code that needs to run as fast as possible.
  
Line 79: Line 119:
  
  
Compiler optimisation levels don't make much difference. All exception blocks come with a small amount of wrapper code that can't be optimised away.
+
Compiler optimisation levels don't make much difference. All exception blocks use a small amount of wrapper code that can't be optimised away. There are different ways for a compiler to produce exception code, with varying performance implications. As of FPC 3.0.4, the default exception mechanism uses the standard setjmp/longjmp style. FPC also supports OS-provided Structured Exception Handling; this is the default on Win64, and can be enabled on Win32 (recompile the compiler with <syntaxhighlight lang="pascal" enclose="none">$define TEST_WIN32_SEH</syntaxhighlight>). Other mechanisms may be added to FPC in the future.
  
To get a better feel for what's going on, try writing a small test program and compiling it with the -a switch. This leaves behind a human-readable assembly file.
+
To get a better feel for what's going on, try writing a small test program and compiling it with the <syntaxhighlight lang="pascal" enclose="none">-a</syntaxhighlight> switch. This leaves behind a human-readable assembly file.
  
* <code>Try</code>-statements insert some extra address calculations, a stack push and pop, some direct jumps, and a conditional jump.
+
Notes on the setjmp/longjmp method:
* <code>Except</code>-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump.
+
* <syntaxhighlight lang="pascal" enclose="none">Try</syntaxhighlight>-statements insert some extra address calculations, a stack push and pop, some direct jumps, and a conditional jump. This is the setup cost of an exception frame, even if no exception is raised.
* <code>Finally</code>-statements add a pop and a conditional jump, to allow re-raising an exception.
+
* <syntaxhighlight lang="pascal" enclose="none">Except</syntaxhighlight>-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump.
* <code>Raise</code>-statements create a string and pass control to FPC's exception raiser.
+
* <syntaxhighlight lang="pascal" enclose="none">Finally</syntaxhighlight>-statements add a pop and a conditional jump.
* <code>Raise</code> statements also create an exception frame for that function/method, even if the Raise statement is not executed. This is why in e.g. container types one often sees the raise command moved to a separate local (private) procedure. That procedure then has the exception frame, but only when actually executed.
+
* <syntaxhighlight lang="pascal" enclose="none">Raise</syntaxhighlight>-statements create a string and pass control to FPC's exception raiser. The string creation itself can spawn an implicit exception frame for the function/method, due to dynamic string allocations, even if the <syntaxhighlight lang="pascal" enclose="none">raise</syntaxhighlight> is not executed. This is why in e.g. container types one often sees the <syntaxhighlight lang="pascal" enclose="none">raise</syntaxhighlight> moved to a separate local (private) procedure. This localises the exception frame to that procedure, so the performance hit is only taken if the procedure is called.
  
Furthermore, if you want a human-readable callstack to aid debugging (using SysUtils functions <code>ExceptAddr</code>, <code>ExceptFrames</code>, <code>ExceptFrameCount</code>, and <code>BackTraceStrFunc</code>), that involves time-consuming stack traversal and string manipulation.
+
Furthermore, generating a human-readable callstack from a raised exception involves time-consuming stack traversal and string manipulation.
  
 
With all that said, outside of heavy processing code, the convenience of exceptions usually outweighs their performance impact. Don't feel bad about using them if it makes your code better.
 
With all that said, outside of heavy processing code, the convenience of exceptions usually outweighs their performance impact. Don't feel bad about using them if it makes your code better.
  
 
== Further reading ==
 
== Further reading ==
 
 
[[try]]
 
  
 
[[Logging exceptions]]
 
[[Logging exceptions]]
Line 102: Line 139:
 
[[Avoiding implicit try finally section]]
 
[[Avoiding implicit try finally section]]
  
[http://codenewsfast.com/cnf/article/0/permalink.art-ng53q201750 Exceptions vs Error codes] - a situation where exceptions might be a danger in some code (see middle of this page)  
+
[http://codenewsfast.com/cnf/article/0/permalink.art-ng53q201750 Exceptions vs Error codes] - a situation where exceptions might be a danger in some code (see middle of this page)
 
 
[[Category:FPC]]
 

Latest revision as of 20:14, 5 July 2019

English (en) suomi (fi)

Free Pascal supports exceptions. Exceptions are useful for error handling and avoiding resource leaks. However, bear in mind that exceptions have a performance impact.

The official documentation is here: Reference guide chapter 17.

By default exceptions are disabled. You can opt in by using the ObjFPC or DELPHI Compiler Mode, or adding -Sx to the compiler commandline. This enables the try, raise, except, and finally reserved words for use in your own code, but it doesn't enable exception generation from the RTL. To enable the RTL to raise exceptions instead of generating runtime errors, use the SysUtils unit in your program.

The base Exception class is found in the SysUtils unit. All exceptions should preferably use this class or its descendants.

SysUtils automatically sets up a basic exception catcher, so any otherwise uncaught exception is displayed in human-readable form, as the program terminates. To generate readable callstacks from caught exceptions in your own code, without necessarily terminating the program, you can use SysUtils functions ExceptAddr, ExceptFrames, ExceptFrameCount, and BackTraceStrFunc.


Examples

Note that Pascal uses different keywords than some other languages: raise instead of throw, and except instead of catch. Also, as a compiler design choice, each try-block can pair with either an except or finally block, but not both at the same time. You'll need to nest try-blocks if you need except and finally protecting the same code.

Error handling

uses sysutils;

begin
  try
    // Do something that might go wrong.
  except
    begin
      // Try to recover or show the user an error message.
    end;
  end;
end.

Cleaning up resources

try
  // Do something that might go wrong.
finally
  // Clean-up code that is always called even if an exception was raised.
end;

Exception leaking

Finally-blocks don't destroy exception objects. Any exceptions that reach the program's "end." will trigger a memory leak warning. An extra except block can be used to consume such exceptions. This is for Delphi-compatibility.

begin
  try
    // Your main program, where an exception object is created.
  finally
    try
      // Clean-up code.
    except
    end;
  end;
end.

Signalling problems

raise Exception.Create('Helpful description of what went wrong');

Using specialised exception types to signal different problems

type EMyLittleException = Class(Exception);

begin
  try
    raise EMyLittleException.Create('Foo');
  except
    on E : EMyLittleException do writeln(E.Message);
    on E : Exception do writeln('This is not my exception!');
    else writeln('This is not an Exception-descendant at all!');
  end;
end;

Re-raising exceptions

try
  // Do something that might go wrong.
except
  // Try to recover or show the user an error message.
  if recoveredSuccessfully = FALSE then
    raise;
end;


Exception classes

SysUtils defines and raises many specific exception classes.

With SysUtils included in your program, and exceptions enabled, various runtime errors are changed into exceptions. Processor-level interrupts like SIGSEGV or SIGFPU, which normally translate to run-time errors, are also changed to exceptions. For example, run-time error 200 (division by zero) becomes EDivByZero or EZeroDivide, while run-time error 216 (a general protection fault) becomes EAccessViolation.


Best practice

  • Raise exceptions to signal that an operation could not be completed, where it normally should have succeeded.
  • Do not use exceptions as part of expected control flow. Instead, add checks for common error conditions and return error values the old-fashioned way. For example, input parsing problems or file existence fails are usually not truly exceptional.
  • But do raise an exception if it's critical that the error is noticed; programmers may forget to check for returned error values.
  • Naming convention: Prefix exception class names with a capital E.
  • Re-raise exceptions in except-blocks using raise; if you were unable to recover from the problem; this preserves the original exception's callstack.
  • Be careful about using the catch-all base Exception class, since the underlying code or OS/filesystem might produce an unanticipated exception that slips through your exception handler, potentially corrupting the program state.
  • When writing a unit or library, if exceptions are used at all, they should be documented clearly up front. This way the unit user knows what to expect.
  • Keep exception handling away from code that needs to run as fast as possible.


Performance

Compiler optimisation levels don't make much difference. All exception blocks use a small amount of wrapper code that can't be optimised away. There are different ways for a compiler to produce exception code, with varying performance implications. As of FPC 3.0.4, the default exception mechanism uses the standard setjmp/longjmp style. FPC also supports OS-provided Structured Exception Handling; this is the default on Win64, and can be enabled on Win32 (recompile the compiler with $define TEST_WIN32_SEH). Other mechanisms may be added to FPC in the future.

To get a better feel for what's going on, try writing a small test program and compiling it with the -a switch. This leaves behind a human-readable assembly file.

Notes on the setjmp/longjmp method:

  • Try-statements insert some extra address calculations, a stack push and pop, some direct jumps, and a conditional jump. This is the setup cost of an exception frame, even if no exception is raised.
  • Except-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump.
  • Finally-statements add a pop and a conditional jump.
  • Raise-statements create a string and pass control to FPC's exception raiser. The string creation itself can spawn an implicit exception frame for the function/method, due to dynamic string allocations, even if the raise is not executed. This is why in e.g. container types one often sees the raise moved to a separate local (private) procedure. This localises the exception frame to that procedure, so the performance hit is only taken if the procedure is called.

Furthermore, generating a human-readable callstack from a raised exception involves time-consuming stack traversal and string manipulation.

With all that said, outside of heavy processing code, the convenience of exceptions usually outweighs their performance impact. Don't feel bad about using them if it makes your code better.

Further reading

Logging exceptions

Avoiding implicit try finally section

Exceptions vs Error codes - a situation where exceptions might be a danger in some code (see middle of this page)