← board

Assigning a managed string (tyAnsiString) into a frozen string (tyString) miscompiles → segfault

Problem

Under PXX_MANAGED_STRING mode the two string-ish kinds disagree on layout but the compiler will silently assign one to the other and emit wrong code:

When the source is tyAnsiString and the destination is tyString — e.g.

function TListBox.Item(AIndex: Integer): string;   { return = frozen tyString }
begin
  Result := FItems[AIndex];   { FItems: array of string -> element = managed tyAnsiString }
end;

the codegen lowers the assignment as a frozen→frozen inline copy: it reads a "length" from offset 0 of the managed handle (which is actually the first 8 characters of the heap string), then rep movsb of that bogus, often huge, length. Result: segfault (exit code 139).

Root cause

The frozen-string store path assumes the source is also a frozen inline string. A tyAnsiString source is a pointer to the managed buffer, not an inline struct, so reading its "length" at +0 and doing an inline byte copy is invalid. The assignment should either:

  1. be rejected at compile time (kind mismatch), or
  2. be coerced — materialise the managed string's chars into the frozen destination's inline buffer (the dual of the frozen→managed assign that already works, e.g. Str's frozen result into an AnsiString).

Today it does neither; it blindly reuses the inline-copy lowering.

Reproduction (shape)

{$define PXX_MANAGED_STRING}
program repro;
var a: array of string; s: string;
begin
  SetLength(a, 1);
  a[0] := 'hello world this is long enough to matter';
  s := a[0];          { managed tyAnsiString -> frozen tyString : miscompiles }
  writeln(s);
end.

Build with the managed-mode flag the PCL GUI suite uses. Expect a segfault / garbage length rather than hello world....

Workaround (already applied, library side)

lib/pcl/stdctrls.pas was changed so TListBox/TComboBox item + text read/write methods, locals, and properties use AnsiString explicitly (e.g. property Text: AnsiString), keeping both sides tyAnsiString so no cross-kind assign happens. GUI suite green under default and managed modes. This masks the symptom; the compiler bug remains.

Fix direction

Part of the string-model arc. Two viable end states:

Acceptance

Log

RESOLVED 2026-06-20 (string-model slice 4p2, commits ca85010/1786e36, pinned v26)

The managed-default flip makes scalar string resolve to tyAnsiString, so array of string and s: string agree and the silent cross-kind assign is gone for normal code. For the genuinely-mixed case a real coercion was added: managed source -> frozen string[N] store materialises the handle's chars into the inline buffer (ir_codegen.inc IR_STORE_SYM frozen path), and the dual frozen->managed (incl. via pointer deref m := p^) materialises a managed handle. Regression test/test_managed_string_flip.pas covers both directions + the original repro; wired into make test. PCL stdctrls.pas can now drop the explicit-AnsiString workaround (6355d7d) — handed to Track B with the v26 re-pin.