← board

A pointer to a fixed array segfaults as a copying open-array argument

Repro

type TA1 = array[3..7] of LongInt; PA1 = ^TA1;
var a1: TA1; p1: PA1; i: LongInt;
procedure Take(const x: array of LongInt);
var k: LongInt;
begin Write('[', Length(x), ']');
  for k := 0 to High(x) do Write(' ', x[k]); WriteLn; end;
begin
  for i := 3 to 7 do a1[i] := i * 2;
  p1 := @a1;
  Take(a1);   { both: [5] 6 8 10 12 14 }
  Take(p1^);  { fpc: [5] 6 8 10 12 14   pxx: prints `[` then SIGSEGV }
end.

Same in every copying mode — const, by value, and var (where the copy-back never happens either).

Cause

StaticArraySourceInfo is "the ONE resolver, called from both the by-value/const arm and the var/out arm" — its own comment says it was two near-identical copies that had already drifted, and that "a third shape would have had to be written into both, and the one that got it second is the one that stays broken." It has arms for AN_IDENT, AN_FIELD, AN_INDEX (the partial N-D row) and AN_SLICE. AN_DEREF is the fifth shape and was never written.

Fix

An AN_DEREF arm asking DerefPtrArrayShape, not a fifth private switch over the two deref spellings (the pointer SYMBOL and the pointee's ArrType row). That reader answered only for rank >= 2 until [[refactor-p-nodearrndinfo-answers-nothing-for-a-rank-1-array]] widened it, and this arm is its first caller — the capability ticket and this bug close together, which is what made the widening worth doing rather than filing.

NDInfoNDims <> 1 refuses a multi-dim pointee here, mirroring the ident arm's SymArrNDims <= 1: an open array is one dimension. The frozen-string capacity comes from DerefPtrArrayElem, which is honest about serving only the deref spelling; it is deliberately not an NDInfo column, because two of that reader's four arms have no element-capacity column to fill.

Gate

test/test_a_pointer_to_a_fixed_array_is_a_copying_open_array_argument.pas — 7 rows, test-core, byte-identical to fpc 3.2.2: const, by value, and var with the assertion on the ORIGINAL (a read-only row would pass with the copy-back broken); a record element, where the stride is visible; the non-zero low bound, which must not leak into the count; the plain-variable control; and row 6, the 2-D-row neighbour that always worked, kept as the boundary.

The positive control is the crash itself: measured at HEAD with the whole change set reverted and the compiler rebuilt, the repro dies with SIGSEGV.

Residual, not fixed here

A SLICE of a deref is refused. Take(p1^[4..6]) answers no overload of Take matches these arguments under pxx and prints [3] 8 10 12 under fpc. That refusal happens at overload matching, before this resolver is reached, so it is a different door — and the AN_SLICE arm recurses into this function, so the slice would work the moment the argument is accepted. Compat, not a wrong value.

Log