fix(iOS): make jsinspector-modern tracing state types move-only for Swift C++ interop - #57605
Closed
chrfalch wants to merge 2 commits into
Closed
fix(iOS): make jsinspector-modern tracing state types move-only for Swift C++ interop#57605chrfalch wants to merge 2 commits into
chrfalch wants to merge 2 commits into
Conversation
…wift C++ interop TraceRecordingState and HostTracingProfile hold std::vector<> of move-only types (RuntimeSamplingProfile, FrameTimingSequence). Their implicit copy constructors are declared but ill-formed on instantiation. Plain C++ never instantiates them, but a Swift target using -cxx-interoperability-mode=default (any Nitro-based library) imports these types via the prebuilt clang modules and its generated bridging uses them as copyable Swift values, forcing instantiation of the ill-formed copy ctor — a hard error on Xcode 26.3 that kills every Swift file in the consumer (e.g. react-native-unistyles in nightly-tests). Declare the types explicitly move-only. Swift then imports them as non-copyable; it is also more correct C++ (these types were never copyable). HostTracingProfile is no longer an aggregate, so its one designated-initializer site is converted to member assignment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The explicit move-only special members added to TraceRecordingState and HostTracingProfile change the public C++ API surface, so the committed cxx-api snapshots must be regenerated (validate_cxx_api_snapshots CI). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cipolleschi
approved these changes
Jul 20, 2026
cipolleschi
left a comment
Contributor
There was a problem hiding this comment.
Thanks for fixing this
|
@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D112802806. |
|
@cipolleschi merged this pull request in 3861118. |
react-native-bot
pushed a commit
that referenced
this pull request
Jul 27, 2026
…wift C++ interop (#57605) Summary: The nightly-tests job `[ios] react-native-unistyles` fails on Xcode 26.3 with: ``` error: no matching function for call to '__construct_at' note: in instantiation of member function 'std::vector<...RuntimeSamplingProfile>::vector' requested here note: in implicit copy constructor for 'facebook::react::jsinspector_modern::tracing::TraceRecordingState' first required here ``` while compiling the **Swift** files of the Unistyles pod. The same failure hits any library built with Swift C++ interop (`-cxx-interoperability-mode=default`) — in practice, every Nitro-based library — against the prebuilt React Native core. ### The error `TraceRecordingState` and `HostTracingProfile` hold `std::vector`s of move-only types (`RuntimeSamplingProfile` and `FrameTimingSequence` explicitly delete their copy constructors). Here's the C++ subtlety: `std::vector<T>`'s copy constructor is **declared for every `T`** — it only becomes ill-formed when *instantiated*. So the implicit copy constructors of these two structs are not implicitly deleted; they exist as declared-but-broken constructors that hard-error the moment anything asks for a copy. ### Why React Native compiles fine today Nothing in RN ever asks. Every usage passes these types by reference; the single constructions move. A pure C++ (or ObjC++) build never instantiates the implicit copy constructors, so this code has always compiled — and always would, no matter how much C++ CI you throw at it. The defect is unobservable from within C++. ### What fails, and why now The prebuilt-core headers now ship as real clang modules. A Swift target with C++ interop imports them (directly or transitively — e.g. via a module member whose `#ifdef __cplusplus` body opens because interop builds modules with C++ enabled), and Swift's ClangImporter surfaces the C++ value types to Swift as copyable. When the consumer's generated interop code then uses such a type as a Swift value — for a Nitro-based library, the nitrogen-generated `*_cxx.swift` bridging does exactly this — the compiler **synthesizes a copy of the type, instantiating the ill-formed implicit copy constructor**. That is the "ask" that plain C++ never makes; on Xcode 26.3 it hard-errors the entire module import, killing every Swift file in the consumer. (Newer Swift toolchains treat such types as non-copyable instead of failing.) Before the prebuilt-modules work there was no Swift-visible module containing these headers, so no interop consumer ever imported these types — which is why this surfaces now despite the C++ being unchanged. We deliberately did **not** fix this by removing headers from the module maps: the guarded-C++-in-modules pattern is shared by ~30 legitimately modular headers and is benign in all but this one shape, and experiments showed the type is reachable through multiple independent module surfaces (removing one member just moved the error to the next path). ### The fix Declare the truth: make both types explicitly move-only. ```cpp TraceRecordingState(const TraceRecordingState &) = delete; TraceRecordingState &operator=(const TraceRecordingState &) = delete; TraceRecordingState(TraceRecordingState &&) = default; TraceRecordingState &operator=(TraceRecordingState &&) = default; ``` With the copy constructor explicitly deleted, Swift's importer sees a non-copyable type and imports it as such instead of instantiating a broken copy. It is also simply more correct C++: these types were never copyable in practice, and the explicit deletion turns any future accidental copy into a clear compile error at the call site instead of a template backtrace. Declaring special members makes `HostTracingProfile` a non-aggregate, so its one designated-initializer construction site (`HostTargetTraceRecording.cpp`) is converted to member-wise assignment. A sweep of the affected header surface (`std::vector`/`std::map`/`std::deque` of move-only element types) found exactly these two types; a follow-up adds a `headers-verify.js` gate that imports the shipped modules under Swift C++ interop at prebuild time, so the next type with this shape fails RN's own CI instead of community nightlies. ## Changelog: [IOS] [FIXED] - Fix Swift C++-interop build failure (implicit copy constructor of TraceRecordingState/HostTracingProfile) for libraries using cxx interop with prebuilt React Native core Pull Request resolved: #57605 Test Plan: On a fresh RN-nightly app with stock `react-native-unistyles@3.3.0` + `react-native-nitro-modules`, prebuilt core (`RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1`), Xcode 26.3: - **Red**: stock headers reproduce the CI failure exactly (`__construct_at` → `TraceRecordingState`). Fixing only `TraceRecordingState` then surfaces the identical failure on `HostTracingProfile` — confirming the shape, not the type, is the bug. - **Green**: with both headers fixed (stock module maps, nothing else changed): BUILD SUCCEEDED — zero `__construct_at`, zero `shadowNodeFromValue`, zero module errors. - All RN-internal usages audited: references and moves only; no behavior change. Plain C++/ObjC++ compilation unaffected by construction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: fabriziocucci Differential Revision: D112802806 Pulled By: cipolleschi fbshipit-source-id: d2ae201c1fbb4de040f51ebc88263c7347b5979b
meta-codesync Bot
pushed a commit
that referenced
this pull request
Jul 27, 2026
…57606) Summary: Adds a generator-time gate so a Swift-C++-interop-hostile C++ type can never silently ship in the prebuilt iOS core headers again. **The class this guards:** the prebuilt core headers ship as real clang modules. A C++ value type reachable from a shipped module whose *implicit copy constructor is declared but ill-formed on instantiation* — e.g. an implicit-copy struct with a `std::vector<move-only-T>` member — compiles fine in C++ but hard-errors when a Swift target with `-cxx-interoperability-mode=default` imports the module and its generated bridging uses the type as a copyable value. This is invisible to C++ CI (plain C++ never instantiates the copy), so it only surfaced as a broken community-library nightly (react-native-unistyles, via `TraceRecordingState` / `HostTracingProfile` — fixed in #57605). **The gate:** a fourth compile gate (stage 3d) in `headers-verify.js`'s `runCompileGates`, compiling a Swift TU with `-cxx-interoperability-mode=default` that imports every module declared by the composed `ReactNativeHeaders` module map (parsed at runtime, not hardcoded), plus a probe that forces the ClangImporter copyability path for a small explicit list of value types (`SWIFT_CXX_INTEROP_PROBE_TYPES`). Because a bare `import` does not eagerly instantiate copy constructors on the current toolchain, the probe reproduces what a real interop consumer's generated code does: for each listed type it conditionally instantiates a copy, which fails exactly as the real consumer fails if the type regresses to an implicit copy. Adding future coverage is a one-line list edit. One module (`React_RCTAppDelegate`) is excluded as non-importable under interop by design (ObjC bootstrap module with C++-only factory conformances); the inspector C++ graph it would reach is covered directly by the probe instead. The exclusion is documented in-source, and new exclusions are required to carry a justification. Independent review confirmed the probe reproduces the real failure for the right reason (the dangerous shape reports `is_copy_constructible_v == true`, so the probe enters the branch and forces the ill-formed body instantiation — mirroring ClangImporter, not masking the bug), and that both offender types are covered.⚠️ **Land after #57605 — the gate runs against the composed artifacts, which are built from source; until the move-only type fixes are in, the gate correctly fails the prebuild on the unfixed types. ## Changelog: [INTERNAL] [ADDED] - Prebuild gate that fails composed iOS headers unusable from a Swift C++-interop consumer Pull Request resolved: #57606 Test Plan: Against a freshly composed Debug artifact (`node scripts/ios-prebuild -c -f Debug`): - **Green**: `node scripts/ios-prebuild/headers-verify.js --flavor Debug` passes the new gate with both probe types (`TraceRecordingState`, `HostTracingProfile`) fixed in the artifact. - **Red, TraceRecordingState**: restoring the unfixed `TraceRecordingState.h` into the composed artifact fails the gate with the exact `__construct_at` / implicit-copy diagnostic; restoring the fix → green. - **Red, HostTracingProfile** (proves coverage isn't accidental): same protocol with `HostTracingProfile` unfixed (TraceRecordingState left fixed) → gate fails naming `HostTracingProfile` and its probe specialization; restore → green. - `node --check`, `prettier --check`, `flow focus-check` clean. Gate is skipped by `--skip-compile` like the other compile gates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: cortinico Differential Revision: D113011048 Pulled By: cipolleschi fbshipit-source-id: 51cfd9fde67c60bce9d13e490fe966ed059432c2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The nightly-tests job
[ios] react-native-unistylesfails on Xcode 26.3 with:while compiling the Swift files of the Unistyles pod. The same failure hits any library built with Swift C++ interop (
-cxx-interoperability-mode=default) — in practice, every Nitro-based library — against the prebuilt React Native core.The error
TraceRecordingStateandHostTracingProfileholdstd::vectors of move-only types (RuntimeSamplingProfileandFrameTimingSequenceexplicitly delete their copy constructors). Here's the C++ subtlety:std::vector<T>'s copy constructor is declared for everyT— it only becomes ill-formed when instantiated. So the implicit copy constructors of these two structs are not implicitly deleted; they exist as declared-but-broken constructors that hard-error the moment anything asks for a copy.Why React Native compiles fine today
Nothing in RN ever asks. Every usage passes these types by reference; the single constructions move. A pure C++ (or ObjC++) build never instantiates the implicit copy constructors, so this code has always compiled — and always would, no matter how much C++ CI you throw at it. The defect is unobservable from within C++.
What fails, and why now
The prebuilt-core headers now ship as real clang modules. A Swift target with C++ interop imports them (directly or transitively — e.g. via a module member whose
#ifdef __cplusplusbody opens because interop builds modules with C++ enabled), and Swift's ClangImporter surfaces the C++ value types to Swift as copyable. When the consumer's generated interop code then uses such a type as a Swift value — for a Nitro-based library, the nitrogen-generated*_cxx.swiftbridging does exactly this — the compiler synthesizes a copy of the type, instantiating the ill-formed implicit copy constructor. That is the "ask" that plain C++ never makes; on Xcode 26.3 it hard-errors the entire module import, killing every Swift file in the consumer. (Newer Swift toolchains treat such types as non-copyable instead of failing.)Before the prebuilt-modules work there was no Swift-visible module containing these headers, so no interop consumer ever imported these types — which is why this surfaces now despite the C++ being unchanged.
We deliberately did not fix this by removing headers from the module maps: the guarded-C++-in-modules pattern is shared by ~30 legitimately modular headers and is benign in all but this one shape, and experiments showed the type is reachable through multiple independent module surfaces (removing one member just moved the error to the next path).
The fix
Declare the truth: make both types explicitly move-only.
With the copy constructor explicitly deleted, Swift's importer sees a non-copyable type and imports it as such instead of instantiating a broken copy. It is also simply more correct C++: these types were never copyable in practice, and the explicit deletion turns any future accidental copy into a clear compile error at the call site instead of a template backtrace.
Declaring special members makes
HostTracingProfilea non-aggregate, so its one designated-initializer construction site (HostTargetTraceRecording.cpp) is converted to member-wise assignment.A sweep of the affected header surface (
std::vector/std::map/std::dequeof move-only element types) found exactly these two types; a follow-up adds aheaders-verify.jsgate that imports the shipped modules under Swift C++ interop at prebuild time, so the next type with this shape fails RN's own CI instead of community nightlies.Changelog:
[IOS] [FIXED] - Fix Swift C++-interop build failure (implicit copy constructor of TraceRecordingState/HostTracingProfile) for libraries using cxx interop with prebuilt React Native core
Test Plan
On a fresh RN-nightly app with stock
react-native-unistyles@3.3.0+react-native-nitro-modules, prebuilt core (RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1), Xcode 26.3:__construct_at→TraceRecordingState). Fixing onlyTraceRecordingStatethen surfaces the identical failure onHostTracingProfile— confirming the shape, not the type, is the bug.__construct_at, zeroshadowNodeFromValue, zero module errors.🤖 Generated with Claude Code