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:
| Code | Text | Raised by |
|---|---|---|
| 1 | the text given | raise(m) |
| 2 | Divide by zero | an integer div or mod by zero |
| 3 | Access violation | a write or read through a nil pointer; a member read through a nil own or ref |
| 4 | Stack overflow | a stack overflow |
| 5 | Numeric overflow | the lowest integer div or mod -1 |
| 10 | what() of a std::exception, else unknown C++ exception | a C++ exception thrown by mixed C++ or a C++ library |
| 11 | set element 20 is out of range 0..9 | a set element outside its set type's range |
| 12 | negative length -1 | setlength(v, n) with a negative n |
| 13 | invalid cast: the object is not a TCircle | X as T on an object that is not a T |
| any | the text given | raisecode(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.
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.
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, andexccode()there is 0.- A float division by zero raises nothing:
1.0 / 0.0isinf.
Nesting and replacing
- An exception leaving an inner guard runs the inner
finally, then reaches the outerexcept. - A raise inside an except body replaces the exception: the guard's
finallyruns, and the outer guard catches the new one. A raise inside a finally body replaces the exception the body raised.
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.
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.
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.
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.
module exe runtime_unguarded;
var
LP: ptr to int32;
begin
println("still in the buffer");
LP := nil;
LP^ := 1;
end.
unhandled exception 3: Access violation
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
getmemgives (getmem(0)gives nil and counts nothing), a blockresizememmakes from nil, eachcreate, and each own or ref object.freemem,destroyand 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
createnever 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.
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.
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.