Skip to content

[release/11.0.1xx] [Foundation] Reuse managed wrappers during constructor chaining - #26835

Merged
rolfbjarne merged 7 commits into
release/11.0.1xxfrom
dev/rolf/backport-pr-26672-release/11.0.1xx-2026-10-02
Oct 3, 2026
Merged

rolfbjarne merged 7 commits into
release/11.0.1xxfrom
dev/rolf/backport-pr-26672-release/11.0.1xx-2026-10-02

Conversation

@rolfbjarne

Copy link
Copy Markdown
Member

Objective-C initializers may chain to another managed constructor on the same native object. The constructor trampoline previously created a second managed wrapper, which attempted to install another GCHandle and emitted:

Tried to create a managed reference from an object that already has a managed reference

Reuse a compatible live wrapper from the native GCHandle ivar instead. Avoid taking a second native reference when the reused wrapper's constructor chain re-enters NSObject, and reject disposed or finalizer-queue wrappers.

Apply the same wrapper-reuse behavior to the managed-static, trimmable-static, and NativeAOT registrar trampolines through a shared generic helper. This also removes the synthetic constructors previously added to user types.

Extend the constructor-chaining regression test to verify that the nested constructor runs on the original wrapper and that the native retain count remains balanced.

Validated with the targeted iOS test using:

  • the default registrar
  • managed-static-registrar
  • release|nativeaot

All configurations passed without the duplicate managed-reference warning.

🤖 Pull request created by Copilot


Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Backport of #26672.

rolfbjarne and others added 7 commits October 2, 2026 19:00
Objective-C initializer chaining can invoke another managed constructor for a native object whose GCHandle ivar already points at the original wrapper. The constructor trampoline previously allocated a second wrapper anyway, which then tried to install another GCHandle and produced the managed-reference warning.

Reuse a compatible wrapper already stored in the native ivar, and let repeated base initialization recognize when the existing GCHandle targets the same managed instance. A failed GCHandle installation still reports a genuinely different wrapper, including the race where another wrapper wins between the lookup and the set.

Extend the constructor-chaining test to verify that the parameterless managed constructor runs on the same instance returned by the outer constructor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid retaining the native object again when an Objective-C initializer
chains into another managed constructor on the same wrapper. The wrapper
already owns the reference transferred by the original allocation, so the
second retain could not be balanced by the single wrapper's disposal path.

Reject disposed and finalizer-queue wrappers during ivar lookup, and preserve
the incoming constructor flags when a live compatible wrapper is reused.

Extend the same behavior to managed-static, trimmable-static, and NativeAOT
registrars. Generated constructor trampolines now call a shared generic helper
to reuse a wrapper from the native gchandle ivar or allocate an uninitialized
one before invoking the real constructor. This replaces the synthetic cloned
constructor previously added to user types.

Assert that constructor chaining retains wrapper identity and leaves the native
object with a retain count of one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7c477a8e-5fc5-4f8e-8edb-a7b7d97cd21b
Only query the native GCHandle ivar when CreateManagedRef is re-entered for a
wrapper that already owns a managed reference. Normal user-type construction
now avoids the additional native message send while constructor chaining still
recognizes and reuses the existing wrapper.

Clear the constructor-chaining test's static instance reference in a finally
block so the disposed wrapper cannot remain rooted or leak state into another
test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7c477a8e-5fc5-4f8e-8edb-a7b7d97cd21b
…tor registrar output

The constructor registrar now emits callbacks that go through NSObject.GetOrCreateNSObjectForConstructor<T>, so the generated code references that method, the HasManagedRef property, and the real NSObjectFlag-based constructor of each type. At the same time the synthetic (IntPtr, IManagedRegistrar) constructors that the old generation scheme required are no longer emitted, so they drop out of the preserved API surface.

These four tvOS expectation files record that intended shift in the registrar output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7c477a8e-5fc5-4f8e-8edb-a7b7d97cd21b
The constructor registrar changes altered which members the registrar preserves, and the tvOS expectation files were already updated to match. Update the eight remaining iOS and Mac Catalyst expectation files so they describe the same output.

The baselines now include NSObject.GetOrCreateNSObjectForConstructor<T>, NSObject.HasManagedRef, and the UIApplicationDelegate NSObjectFlag constructor. They remove the obsolete synthetic (IntPtr, IManagedRegistrar) constructors, including ActionDispatcher on Mac Catalyst.

This addresses review feedback and prevents the app-size preserved API tests from failing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7c477a8e-5fc5-4f8e-8edb-a7b7d97cd21b
The previous cross-platform baseline update covered the shared registrar changes but did not match the generated CI output exactly. Apply the remaining differences verbatim from the Azure DevOps updated-expected-sizes artifacts.

The Mac Catalyst CoreCLR baselines now preserve the ActionDispatcher static constructor, EventHandler constructor, and Action and DoubleAction selector fields. The iOS NativeAOT and NativeAOT-TrimmableStatic expected app sizes also record the approximately 15 KB cost of the constructor registrar changes.

Using the CI artifacts keeps these app-size and preserved-API expectations authoritative.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7c477a8e-5fc5-4f8e-8edb-a7b7d97cd21b
Copilot AI balanced review requested due to automatic review settings October 2, 2026 18:19
@rolfbjarne
rolfbjarne requested a review from dalexsoto as a code owner October 2, 2026 18:19
@rolfbjarne rolfbjarne added backported ready-to-review This PR is ready to review/merge. labels Oct 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Broad constructor preservation can unnecessarily inflate trimmed and NativeAOT applications.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Backports constructor-wrapper reuse to prevent duplicate GCHandles during Objective-C constructor chaining.

Changes:

  • Reuses compatible live NSObject wrappers across registrar modes.
  • Avoids duplicate native retains and synthetic constructors.
  • Extends regression tests and updates size/API baselines.
File Description
src/​Foundation/​NSObject2.cs Adds wrapper reuse and retain handling.
tools/​dotnet-linker/​AppBundleRewriter.cs Resolves the shared constructor helper.
tools/​dotnet-linker/​Steps/​ManagedRegistrarStep.cs Emits helper-based constructor trampolines.
tests/​monotouch-test/​ObjCRuntime/​RegistrarTest.cs Verifies wrapper identity and retain count.
tests/​dotnet/​UnitTests/​expected/​TVOS-MonoVM-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​TVOS-MonoVM-interpreter-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​TVOS-CoreCLR-R2R-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​TVOS-CoreCLR-Interpreter-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​MacOSX-NativeAOT-TrimmableStatic-size.txt Updates size baseline.
tests/​dotnet/​UnitTests/​expected/​MacOSX-NativeAOT-size.txt Updates size baseline.
tests/​dotnet/​UnitTests/​expected/​MacOSX-CoreCLR-R2R-size.txt Updates size baseline.
tests/​dotnet/​UnitTests/​expected/​MacOSX-CoreCLR-Interpreter-TrimmableStatic-size.txt Updates size baseline.
tests/​dotnet/​UnitTests/​expected/​MacOSX-CoreCLR-Interpreter-size.txt Updates size baseline.
tests/​dotnet/​UnitTests/​expected/​MacCatalyst-MonoVM-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​MacCatalyst-MonoVM-interpreter-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​MacCatalyst-CoreCLR-R2R-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​MacCatalyst-CoreCLR-Interpreter-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​iOS-NativeAOT-TrimmableStatic-size.txt Updates size baseline.
tests/​dotnet/​UnitTests/​expected/​iOS-NativeAOT-size.txt Updates size baseline.
tests/​dotnet/​UnitTests/​expected/​iOS-MonoVM-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​iOS-MonoVM-interpreter-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​iOS-CoreCLR-R2R-preservedapis.txt Updates preserved APIs.
tests/​dotnet/​UnitTests/​expected/​iOS-CoreCLR-Interpreter-preservedapis.txt Updates preserved APIs.

Comment thread src/Foundation/NSObject2.cs
@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ API diff for current PR / commit

NET (empty diffs)

✅ API diff vs stable

NET (empty diffs)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: 6552ff6e24551b3c7641573a889a98dd28cb8461 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🚀 [CI Build #6552ff6] Test results 🚀

Test results

✅ All tests passed on VSTS: test results.

🎉 All 269 tests passed 🎉

Tests counts

✅ assembly-processing: All 1 tests passed. Html Report (VSDrops) Download
✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 4 tests passed. Html Report (VSDrops) Download
✅ generator: All 5 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 7 tests passed. Html Report (VSDrops) Download
✅ linker (iOS): All 31 tests passed. Html Report (VSDrops) Download
✅ linker (MacCatalyst): All 31 tests passed. Html Report (VSDrops) Download
✅ linker (macOS): All 21 tests passed. Html Report (VSDrops) Download
✅ linker (tvOS): All 31 tests passed. Html Report (VSDrops) Download
✅ monotouch (iOS): All 25 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 25 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 20 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 25 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ sharpie: All 1 tests passed. Html Report (VSDrops) Download
✅ windows: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

macOS tests

✅ Tests on macOS Sonoma (14): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Sequoia (15): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Tahoe (26): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Golden Gate (27): All 5 tests passed. Html Report (VSDrops) Download

Linux Build Verification

✅ Linux build succeeded

Pipeline on Agent
Hash: 6552ff6e24551b3c7641573a889a98dd28cb8461 [PR build]

@rolfbjarne
rolfbjarne merged commit 89cbdeb into release/11.0.1xx Oct 3, 2026
56 checks passed
@rolfbjarne
rolfbjarne deleted the dev/rolf/backport-pr-26672-release/11.0.1xx-2026-10-02 branch October 3, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backported ready-to-review This PR is ready to review/merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants