Exceptions

PXX implements setjmp-style exceptions with a per-stack handler chain. It supports both exception handling (try ... except) and resource cleanup (try ... finally).

Raising and Handling Exceptions

Raising an Exception

Use the raise statement followed by a class instance to raise an exception:

raise TMyException.Create;

Inside an except block, a bare raise statement re-raises the active exception:

except
  on E: TMyException do
  begin
    Log(E);
    raise; { re-raise }
  end;
end;

Try-Except Blocks

A try ... except ... end block catches exceptions raised within the try clause. You can use typed handlers to catch specific exception classes:

try
  DoSomething;
except
  on E: TSpecificException do HandleSpecific(E);
  on E: TObject do HandleFallback(E);
end;

Try-Finally Blocks

A try ... finally ... end block ensures that the statements in the finally clause execute regardless of whether an exception was raised inside the try clause. This is typically used for resource cleanup:

var
  doc: TDocument;
begin
  doc := TDocument.Create;
  try
    doc.Process;
  finally
    doc.Free; { always executes }
  end;
end;

Key Characteristics & Limitations

1. Any Class Is Raisable; a Standard Hierarchy Ships in sysutils

The raise mechanism itself is class-agnostic: any class instance can be raised and caught, so a small program can define and raise its own bare class without importing anything (as the example below does).

For FPC-style code, uses sysutils brings in a base Exception class (with a Message property and a message constructor) and a standard descendant family: EAbort, EConvertError, EInOutError, EAccessViolation, EIntError (→ EDivByZero, ERangeError, EIntOverflow), EMathError, EOutOfMemory, and more. The runtime checks raise the matching ones — {$R+} raises ERangeError, {$Q+} raises EIntOverflow, and integer division by zero raises EDivByZero. The classes and json units add their own (EStreamError, EJSONError, …).

What is not bundled is FPC's complete RTL exception surface — only the subset above and the units' own classes are present.

2. Automatic Resource Cleanup (Unwinding)

When an exception is raised, the runtime unwinds the stack frames until a matching handler is found. During unwinding, the compiler automatically calls the appropriate cleanup code (releasing reference counts) for any managed local variables (such as managed strings, dynamic arrays, and records with managed fields) in the unwound frames, preventing memory leaks.

3. Unhandled Exceptions

If an exception is raised and no handler catches it, the runtime's default unhandled-exception reporter prints a diagnostic message to standard error and terminates the program with a non-zero exit code.

4. Interaction with Generators

Because exception frames are tied to the call stack, calling yield from within a try, except, or finally block inside a generator is rejected by the compiler.


Compiling Example

The following example compiles and runs on the pinned compiler:

program exceptions_demo;

type
  TValidationException = class
  private
    FReason: string;
  public
    constructor Create(const AReason: string);
    property Reason: string read FReason;
  end;

constructor TValidationException.Create(const AReason: string);
begin
  FReason := AReason;
end;

procedure ValidateAge(Age: Integer);
begin
  if (Age < 0) or (Age > 150) then
    raise TValidationException.Create('Age is out of realistic range');
end;

procedure RunValidator;
begin
  try
    writeln('Validating age...');
    ValidateAge(200);
    writeln('Validation passed.'); { will not run }
  except
    on E: TValidationException do
    begin
      writeln('Validation failed: ', E.Reason);
      E.Free; { free the exception instance }
    end;
  end;
end;

begin
  RunValidator;
end.

Output:

Validating age...
Validation failed: Age is out of realistic range