← board

C: ternary with two string-literal arms segfaults at runtime

Symptom

A cond ? "lit" : "lit" whose arms are string literals compiles "ok" but segfaults when the value is used:

extern long write(int, const void *, unsigned long);
int main(void){
  int u = 1;
  const char *d = u ? "ABCDEF" : "abcdef";
  write(1, d, 6);                  /* SIGSEGV */
  return 0;
}

Cause

ir.inc AN_TERNARY lowering retags a tyString result arm to managed tyAnsiString (so the per-arm assignment coerces the frozen literal into a managed temp and the load yields a real handle). That is correct for Pascal's managed-default string model, but in C mode a string literal is a frozen char*/PChar, not a managed AnsiString — coercing it through the ARC assign path produces a garbage handle that the consumer dereferences -> crash.

The lowering needs a CProgramMode branch: a C string-literal ternary arm must carry the literal's address (char*) like the rest of the C string model, not go through the managed-string temp.

Workaround in the meantime

Avoid string-literal ternary in C source; use if/else. lib/crtl/src/stdio.c is written this way already.

Acceptance

The repro prints ABCDEF and exits 0.

DONE 2026-06-26 (Track A+C combined)

Fixed in ir.inc AN_TERNARY lowering: the tyString -> tyAnsiString retag now has a CProgramMode branch — in C a string-literal arm is carried as a plain tyPointer (the per-arm assign stores the literal's address, the load yields the char*), only Pascal routes through the managed AnsiString temp. CProgramMode gated, so Pascal self-compile is byte-identical. Both repros pass (0X, ABCDEF). This was THE blocker for the entire crtl printf engine — stdio.c's prefix = (k=='X') ? "0X" : "0x" (NOT actually avoided as this ticket claimed) corrupted __crtl_vformat's frame, making snprintf re-enter ~7400x to stack overflow. With it fixed, printf %d/%x/%s/%c/%p + width/precision all render == gcc. (Remaining printf gap: %f/%g = bug-c-double-vararg.)