Skip to content

link(macos): temporal gap tests fail with undefined _CFRelease — runtime-only links drop CoreFoundation but iana_time_zone needs it (#6923 fallout) #6947

Description

@proggeramlug

All 7 test_gap_temporal_*.ts gap tests fail to link on macOS at current main (0bb03e82f), with an undefined _CFRelease from iana_time_zone.

This is release-gating (the parity suites gate release-packages.yml) and is invisible to PR CI, because parity and compile-smoke are tag-push-only. It was found incidentally while running the full gap sweep for #6925.

Symptom

Linking (runtime-only)...
Undefined symbols for architecture arm64:
  "_CFRelease", referenced from:
      __RNvNtCsfzQT8WKhDmi_14iana_time_zone8platform18get_timezone_inner
        in libperry_runtime.a[49](...iana_time_zone...rcgu.o)
  "_CFStringGetBytes", ...
  "_CFStringGetCStringPtr", ...

Affected (7/7 temporal gap tests):

test_gap_temporal_construct_overflow    test_gap_temporal_options
test_gap_temporal_duration              test_gap_temporal_plaindate_plaintime
test_gap_temporal_instant_zoned         test_gap_temporal_plaindatetime
test_gap_temporal_now

Root cause

crates/perry/src/commands/compile/link/build_and_run.rs (~line 939) gates the macOS framework list on:

if (cfg!(target_os = "macos") || is_cross_macos)
    && !is_harmonyos
    && (ctx.needs_stdlib || ctx.needs_ui || ctx.needs_plugins || ctx.needs_geisterhand)
{
    cmd.arg("-framework").arg("Security")
       .arg("-framework").arg("CoreFoundation")
       .arg("-framework").arg("SystemConfiguration")
       .arg("-liconv").arg("-lresolv").arg("-lobjc");
}

A Temporal program is runtime-only — none of those four flags is set — so it links without CoreFoundation. But perry-runtime built with the temporal feature pulls in iana_time_zone, whose macOS timezone lookup calls CFRelease / CFStringGetBytes / CFStringGetCStringPtr. Hence the undefined symbols.

The condition needs a fifth term for the temporal/iana_time_zone surface (or, more narrowly, CoreFoundation alone when that feature is compiled in).

Introduced by #6923 (db6b2e2a4, "drop unused framework dylibs from runtime-only macOS binaries"). That change is a good win (~1.8 ms of the ~3 ms hello startup) and its own comment anticipates this exact failure mode:

"Skip the whole list for those links: a runtime-only program that genuinely needs one of these fails AT LINK TIME with an undefined symbol (deterministic, caught by the parity suites), never at runtime."

The reasoning is sound — the failure is deterministic and link-time. The gap is that the parity suites which would catch it don't run per-PR.

Attribution / how it was isolated

Not caused by #6925 (repsel Phase 5a); three independent checks:

  1. Identical failures with that PR's phase fully disabled (PERRY_PTR_SHAPE_THIS=0 PERRY_PTR_SHAPE_LOCALS=0) — 7/7 fail in both arms.
  2. build_and_run.rs is not in that PR's diff; the failing code path is byte-identical to main.
  3. The PR's diff contains zero linking/framework references (grep -icE "framework|CoreFoundation|link_arg|dylib" → 0).

Suggested fix

Add the temporal surface to the gate, e.g. a ctx.needs_temporal (or reuse whatever signals the temporal runtime feature) so CoreFoundation is linked when iana_time_zone is present. Keeping the other frameworks off preserves most of #6923's startup win for the common runtime-only case.

Secondary suggestion

Consider running a small representative gap shard on PRs — the temporal group is 7 files and would have caught this within a minute. The full suite staying tag-gated is reasonable; a smoke subset is not expensive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions