Skip to content
PaskalCPP
Build and run

The runtime

What happens while a program runs: the exceptions a guard catches, with their codes and texts; what happens to an exception no guard catches; and the debug build's heap report.

The guard statement itself, and how jumps leave it, are on guard.

What a guard catches

A hardware fault inside a guard body is caught as an exception, and the program goes on after the guard; a second fault of the same kind is caught again. The codes and texts are the same on both official targets:

CodeTextRaised by
1the text givenraise(m)
2Divide by zeroan integer div or mod by zero
3Access violationa write or read through a nil pointer; a member read through a nil own or ref
4Stack overflowa stack overflow
5Numeric overflowthe lowest integer div or mod -1
10what() of a std::exception, else unknown C++ exceptiona C++ exception thrown by mixed C++ or a C++ library
11set element 20 is out of range 0..9a set element outside its set type's range
12negative length -1setlength(v, n) with a negative n
13invalid cast: the object is not a TCircleX as T on an object that is not a T
anythe text givenraisecode(c, m)

The fault rows (2 to 5) are checked in a debug build. The nil-access row holds at every optimize level: in a safe or fast build, a write or read through a nil pointer the optimizer can see is still a real access, and the guard catches exception 3.

runtime_guards.pcpp
module exe runtime_guards;

#include <stdexcept>

static void runtime_guards_std() {
  throw std::runtime_error("bad thing");
}

static void runtime_guards_int() {
  throw 42;
}

type
  TDigits = set of 0..9;

routine Deep(AN: int32): int32;
var
  LBuf: array[0..1023] of int64;
begin
  if AN = 0 then
    return 0;
  end;
  LBuf[0] := AN;
  return Deep(AN - 1) + int32(LBuf[0] mod 2);
end;

routine Show(const AWhat: string; ACode: int32; const AMsg: string);
begin
  println("{}: {} {}", AWhat, ACode, AMsg);
end;

var
  LP: ptr to int32;
  LZ: int32;
  LLow: int32;
  LN: int32;
  LK: int32;
  LS: TDigits;
  LF: float64;

begin
  LP := nil;
  LZ := 0;
  LLow := -2147483647 - 1;
  LK := 20;

  // a write through nil
  guard
    LP^ := 1;
  except
    Show("nil write", exccode(), excmsg());
  end;

  // integer division by zero
  guard
    LN := LLow div LZ;
  except
    Show("div 0", exccode(), excmsg());
  end;

  guard
    LN := LLow mod LZ;
  except
    Show("mod 0", exccode(), excmsg());
  end;

  // the lowest int32 divided by -1 overflows
  LN := -1;
  guard
    LN := LLow div LN;
  except
    Show("low div -1", exccode(), excmsg());
  end;

  LN := -1;
  guard
    LN := LLow mod LN;
  except
    Show("low mod -1", exccode(), excmsg());
  end;

  // a stack overflow, caught twice
  guard
    LN := Deep(1000000);
  except
    Show("deep", exccode(), excmsg());
  end;

  guard
    LN := Deep(1000000);
  except
    Show("deep again", exccode(), excmsg());
  end;

  // C++ exceptions thrown by the mixed C++ above
  guard
    runtime_guards_std();
  except
    Show("c++ std", exccode(), excmsg());
  end;

  guard
    runtime_guards_int();
  except
    Show("c++ int", exccode(), excmsg());
  end;

  // a set element outside the set type's range
  guard
    LS := [LK];
  except
    Show("set", exccode(), excmsg());
  end;

  // raises of your own
  guard
    raise("plain");
  except
    Show("raise", exccode(), excmsg());
  end;

  guard
    raisecode(0, "zero");
  except
    Show("raisecode 0", exccode(), excmsg());
  end;

  guard
    raisecode(-5, "neg");
  except
    Show("raisecode -5", exccode(), excmsg());
  end;

  // a float division by zero raises nothing
  LF := 1.0;
  LF := LF / 0.0;
  println("float / 0.0: {}", LF);

  // the program went on: a fault is caught again
  guard
    LP^ := 2;
  except
    Show("nil again", exccode(), excmsg());
  end;
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
nil write: 3 Access violation
div 0: 2 Divide by zero
mod 0: 2 Divide by zero
low div -1: 5 Numeric overflow
low mod -1: 5 Numeric overflow
deep: 4 Stack overflow
deep again: 4 Stack overflow
c++ std: 10 bad thing
c++ int: 10 unknown C++ exception
set: 11 set element 20 is out of range 0..9
raise: 1 plain
raisecode 0: 0 zero
raisecode -5: -5 neg
float / 0.0: inf
nil again: 3 Access violation
[Heap] Allocs: 0, Frees: 0, Leaked: 0
  • A stack overflow is caught, and caught again by the next guard: the stack's guard page is re-armed after each one.
  • raisecode(c, m) takes any int32, 0 and negative codes included: raisecode(0, "zero") still runs the except body, and exccode() there is 0.
  • A float division by zero raises nothing: 1.0 / 0.0 is inf.

Nesting and replacing

  • An exception leaving an inner guard runs the inner finally, then reaches the outer except.
  • A raise inside an except body replaces the exception: the guard's finally runs, and the outer guard catches the new one. A raise inside a finally body replaces the exception the body raised.
runtime_replace.pcpp
module exe runtime_replace;

begin
  guard
    guard
      raisecode(1, "first");
    finally
      println("inner finally");
    end;
  except
    println("caught {} {}", exccode(), excmsg());
  end;

  guard
    guard
      raisecode(4, "first");
    except
      raisecode(5, "second");
    finally
      println("finally of the second");
    end;
  except
    println("caught {} {}", exccode(), excmsg());
  end;

  guard
    guard
      raisecode(6, "body");
    finally
      raisecode(8, "from finally");
    end;
  except
    println("caught {} {}", exccode(), excmsg());
  end;
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
inner finally
caught 1 first
finally of the second
caught 5 second
caught 8 from finally
[Heap] Allocs: 0, Frees: 0, Leaked: 0

Unhandled exceptions

A raise or C++ exception no guard catches ends the program: every finally on its way runs, then the runtime prints unhandled exception <code>: <text> to stderr, after everything the program printed before it, and the exit code is 1, on both official targets. Nothing after it runs: no finalize, no heap report.

runtime_unhandled.pcpp
module exe runtime_unhandled;

finalize
begin
  println("finalize never runs");
end;

begin
  println("before");
  guard
    raisecode(7, "boom");
  finally
    println("finally runs");
  end;
  println("never printed");
end.
output on x86_64-windows-gnu and x86_64-linux-gnu, exit code 1
before
finally runs
unhandled exception 7: boom

A hardware fault outside every guard ends the program the same way: unhandled exception <code>: <text> on stderr (a nil write is 3: Access violation, a stack overflow 4: Stack overflow), exit code 1, on both official targets.

runtime_unguarded.pcpp
module exe runtime_unguarded;

var
  LP: ptr to int32;

begin
  println("still in the buffer");
  LP := nil;
  LP^ := 1;
end.
output on x86_64-windows-gnu and x86_64-linux-gnu, exit code 1
unhandled exception 3: Access violation
Warning

Output the program printed that still waits in stdout's buffer, when stdout is a pipe or a file, is lost when a fault ends the process: the fault ends it from a signal or exception handler, where no flush is safe.

A failed assert outside a test also ends the program with exit code 1: see Asserts outside a test.

The heap report

A debug build prints [Heap] Allocs: <n>, Frees: <n>, Leaked: <n> on stdout when the program ends, after finalize; a release build prints nothing.

  • Counted: each block getmem gives (getmem(0) gives nil and counts nothing), a block resizemem makes from nil, each create, and each own or ref object. freemem, destroy and an object's end count as frees. Dynamic arrays (setlength) are not counted.
  • After the counts, every own or ref object still alive and every create never destroyed gets a line [Heap] Leaked object created at <file name>(<line>), naming the line that built it, in file and line order: a ref cycle is never freed.
runtime_heap.pcpp
module exe runtime_heap;

forward type TNode;

type
  TNode = object
    Next: ref TNode;
  end;

routine Cycle();
var
  LX: ref TNode;
  LY: ref TNode;
begin
  LX := ref TNode;
  LY := ref TNode;
  LX.Next := LY;
  LY.Next := LX;
end;

routine Plain();
var
  LO: own TNode;
begin
  LO := own TNode;
end;

var
  LKept: ptr;
  LFreed: ptr;
  LNone: ptr;
  LGrown: ptr;
  LD: array of int32;

finalize
begin
  println("final");
end;

begin
  LKept := getmem(16);
  LFreed := getmem(32);
  freemem(LFreed);
  LNone := getmem(0);
  LGrown := resizemem(nil, 8);
  setlength(LD, 100);
  Plain();
  Cycle();
  println("end of body");
end.
output on x86_64-windows-gnu and x86_64-linux-gnu
end of body
final
[Heap] Allocs: 6, Frees: 2, Leaked: 4
[Heap] Leaked object created at runtime_heap.pcpp(44)
[Heap] Leaked object created at runtime_heap.pcpp(45)

Output

print and println write UTF-8 text; a wchar or wstring prints as UTF-8, the same bytes on both official targets.