← board

d.Foo; on an all-defaulted virtual method segfaults

Measured

type TBase = class
  n: Integer;
  procedure D(a: Integer = 3); virtual;
end;
var d: TBase;
begin d := TBase.Create; d.D; WriteLn(d.n); end.
spelling fpc 3.2.2 pxx (before)
d.D;virtual, all-defaulted, NO parens 3 SEGFAULT
d.D(); — empty parens 3 3
d.D(5); — explicit 5 5
d.Z; — virtual, ZERO parameters 42 42
d.D; where D is non-virtual 3 3

Why it survived: every neighbour works

The crashing cell is the only one. A zero-parameter method is fine because there is nothing to fill; empty parens are fine because that arm was added deliberately (for J.FormatJSON()); explicit arguments are fine; and the NON-virtual spelling of the identical source is fine. So any test written "nearby" passes, and the shape that crashes is the one whose siblings all work.

Cause

In ParseLValueAST's instance-method branch, argument parsing is if CurTok.Kind = tkLParen then begin ... end; — with no else. With no parentheses the call node went out carrying only Self, the arity was short by the defaulted parameters, and the callee read an uninitialised frame slot. On a virtual call that is a fault; non-virtual happened to survive it.

The trap inside the fix, worth more than the fix

The obvious repair is else if CanFillDefaultsFrom(mpi, 1) then FillDefaultArgs(...). That does nothing, and it compiles and self-hosts while doing nothing. CanFillDefaultsFrom is

Result := (CurTok.Kind = tkRParen) and ... and ProcParamHasDefault[...];

so it really answers "the argument list ENDS HERE and the rest can default" — two questions under one name. With no parentheses CurTok is ;, so it answers False for a method whose parameters plainly do have defaults. The first attempt at this fix did exactly that and the segfault was unchanged.

The arm therefore tests ProcParamHasDefault directly. CanFillDefaultsFrom is left alone: eight call sites depend on its tkRParen half, and splitting it is a separate change. Noted here because the next person to add a defaults arm will reach for it too.

Verified

test/test_inherited_and_parenless_defaults.pas, byte-identical against fpc 3.2.2, with all five rows above plus the partial-argument case (b.Foo(7) → the second parameter defaults). gate.sh quick GREEN.