Measured 2026-09-06, compiler c73cfd487af1 (commit 393fe0184)
type TT = string[10]; PT = ^TT; { and the reverse order, and PT = ^string[10] }
var v: TT; p: PT;
SizeOf(v) -> 11 = fpc { frankB's fix }
SizeOf(p^) -> 8 fpc 11 { this ticket }
p := @v; v := 'abc'; p^ and Length(p^) are both CORRECT
The VALUE through the pointer is right; only the size is wrong. That is what
makes it the FillChar/Move shape rather than a visible one.
Why it is filed and not folded into the forward-pointer fix
Found while fixing ResolvePendingPointerAliases' scalar/set arm, whose whole
signature is order-dependent. This one is not: the in-order spelling answers 8
as well, so it was never the pending-alias pass's business. Folding it in would
have put a second, unrelated cause under one commit's positive control.
test_a_forward_pointer_to_a_scalar_or_set_alias_keeps_its_pointee.pas
deliberately asserts the STORE through a ^string[4] and not the SizeOf, so it
makes no claim it cannot keep.
Resolved 2026-09-06 — frankB, Group 19 ("how many bytes is this type", asked through a door that never reaches the capacity table)
Fixed, and not the way this ticket proposed. The ticket said the expression path already consults three shape-specific sizers before its fallthrough and this is the fourth. It is the fourth arm, but the arm the ticket described would have been WRONG, and the reason is the group's whole finding.
The obvious repair answers 24, not 11
SizeOfSlot(szExprTk, FrozenStrCapOfDeref(node)) supplies the missing capacity
and leaves the kind alone. An ident node for a frozen string carries the
legacy overloaded tyString whatever the symbol holds — measured on the
sibling ticket with a PXXDBG p.fiosize probe, nodeTk=4 symTk=25 symCap=10,
and confirmed here with a p.szexpr probe printing tk=4 for both a
^string[10] and a ^string[300]. tyString's length prefix is eight bytes
and a shortstring's is one, so that spelling answers AlignTo(10+8,8) = 24.
A loud wrong answer replacing a quiet one.
Both facts were already recorded, on the same symbol
Syms[sym].PtrElemTk the pointee's KIND probed: 25 / 26
SymPtrElemStrCap[sym] the pointee's CAPACITY probed: 10 / 300
DerefFrozenStrSlotSize (symtab.inc, beside FrozenStrCapOfDeref) reads both
out of that one symbol and calls SizeOfSlot. It is deliberately one function
rather than a capacity reader plus a kind reader at each call site — the
invariant the group established is that the kind and the capacity must come
out of the same record, and a helper is where an invariant can be stated once.
Why the wide kind is in the test
string[N] is tyShortString up to 255 and tyFixedString above it, and
tyFixedString's prefix is also eight bytes. So the wrong kind is right by
coincidence above the boundary, and a test that only carries string[10]
cannot tell a correct sizer from a lucky one. Row G asserts 312 for a
^string[300].
Verified
| row | pxx | fpc 3.2.2 | |
|---|---|---|---|
| A | PT = ^TT; TT = string[10] |
11 | 11 |
| B | PE = ^TU; TU = string[7] (pointer declared first) |
8 | 8 |
| C | PS = ^string[10] direct |
11 | refused: local type definition |
| G | PW = ^TW; TW = string[300] |
312 | refused: shortstring capped at 255 |
| D | SizeOf(v) control |
11 | 11 |
| E | FillChar(p^, SizeOf(p^), 0) then Length(v) |
0, guard intact | 0, guard intact |
| F | Move(p^, g, SizeOf(p^)) |
whole slot | whole slot |
Rows A, B, D, E, F are byte-identical to fpc's own output for the same program. E and F are the consequence rather than the number: before the fix the FillChar cleared 8 of 11 bytes.
make compiler/pascal26: converged after 1 round(s).
One correction to this ticket's own body
It says the defect reproduces "for both the PT = ^TT and the direct
PT = ^string[10] spellings", which is true of us and reads as though both are
FPC-comparable. They are not: fpc refuses ^string[N] in a type block
outright — Parameters or result types cannot contain local type definitions
— so the direct spelling is ours alone and has no oracle. Noted because the
ticket's evidence is otherwise stated against fpc.
Log
- 2026-09-06 — resolved; this names the commit that carried the resolve, which is not always the one that carried the change — commit 226f6902e.