← board

A fresh dyn-array result passed to an open-array param is never freed

Measured

Each shape as its own program, 1000 trips, -dPXX_ALLOC_CENSUS:

shape allocs frees live
SumC(MkIA(i))const a: array of Integer 921 0 921
SumV(MkIA(i)) — by-value a: array of Integer 921 0 921
CntS(MkArr(i))const a: array of AnsiString 921 0 921
SumC(iv) — NAMED array 1 0 1
SumC([1,2,3,4]) — literal 921 918 3
SumC(Copy(MkIA(i))) — already owned 1871 1868 3

frees=0, not "frees fewer than allocs". Nothing released these at all, and passing an array to a routine is about as common a shape as Pascal has.

It leaks for array of Integer as well as array of AnsiString, so it is the HANDLE with no owner, not the elements — the same signature as the Copy/concat case.

Why the existing test did not see it

test_open_array_no_leak.pas passes NAMED STATIC arrays (sa: array[0..2] of AnsiString, ia: array[0..2] of Integer) a million times. Those have an owner by construction, and the static-array arms in IRLowerCallArg build their own owned dyn-array temp for the large case. The leak needs a dyn-array RVALUE, which nothing in the corpus passed to an open array. Not a duplicate, and not a gap anyone could have spotted by reading the test's name.

Fix

IRLowerCallArg (ir.inc), after the AN_COMMA unwrap so argAST is final and after the static-array arms so they keep their own path:

if (cpi >= 0) and (pathIdx >= 0) and (pathIdx < Procs[cpi].ParamCount) and
   Procs[cpi].Params[pathIdx].IsArray and (not Procs[cpi].Params[pathIdx].IsRef) and
   (NodeDynDepth(argAST) > 0) then
begin
  Result := IRParkManagedDyn(IRLowerAST(argAST), argAST);
  Exit;
end;

IsRef is excluded because a var/out open array must alias the caller's array; a call result is not an lvalue and cannot reach that path anyway.

Controls, and why they are the point

A park that fires too eagerly double-frees, so the three shapes that were already clean matter as much as the three that leaked. IRParkManagedDyn returns its value unchanged unless IRNodeOwnsFreshCallResult — so a named array, a literal and an already-parked Copy(...) are untouched. All three measured identical before and after, and every sum in the test is asserted against the value measured BEFORE the fix, so a park that freed something still in use shows up as a wrong number rather than only as a crash.

Regression test test/test_open_array_fresh_result_leaks, wired into test-core. Positive control: live=1504 with the fix reverted, 7 with it, bound 50. Note the value assertions pass EITHER WAY — assert_no_leak.sh is the row that catches this, exactly as for [[bug-a-a-fresh-array-result-has-no-owner-as-a-copy-or-concat-operand]].

Log