← board

Length() / High() on an open-array parameter return 0 / -1

Repro

program OA;
procedure P(var a: array of AnsiString);
begin
  writeln('Length(open array) = ', Length(a), '  High = ', High(a));
end;
var arr: array[0..9] of AnsiString;
begin
  P(arr);
end.
Length(open array) = 0  High = -1

FPC prints 10 and 9. The array passed is a fixed array[0..9], so the bounds are statically known at the call site; they are simply not reaching the callee.

Why it matters

Length(a) / High(a) inside a routine taking array of T is the idiomatic way to write a bounds-safe helper in Pascal — it is how every "fill this buffer, tell me how many" API is written. With this bug:

All three fail silently and plausibly: the function returns 0 results, which reads as "nothing found" rather than "the loop never ran". In the case that found it, a PEM bundle with 121 certificates parsed to 0 anchors, and the natural next suspicion was the parser, not the loop bound. That is the expensive kind of bug — it sends you looking at the wrong component.

It is also a security-relevant shape in this instance: the consumer is a TLS trust store, and "loaded 0 trusted roots" happens to fail closed here only because the code explicitly treats an empty store as trusting nothing. Written slightly differently — say, "no roots loaded, skip verification" — the same bug would fail open.

Scope to check

Workaround in the meantime

Pass the capacity explicitly as a second parameter. lib/rtl/truststore.pas does this (PemSplit(..., ders, cap)) with a comment pointing here; it is a defensible API on its own terms, which is why it was preferred over indexing tricks, but it should revert to Length(ders) once this is fixed.

Acceptance

[[feature-tls-system-trust-store]] · lib/rtl/truststore.pas (the workaround and its comment).

Log