Skip to content

[dotnet-linker] Don't preserve base type members for [Preserve (AllMembers = true)]. - #26362

Open
rolfbjarne wants to merge 1 commit into
dev/rolf/disposerfrom
dev/rolf/preserve-all-members
Open

[dotnet-linker] Don't preserve base type members for [Preserve (AllMembers = true)].#26362
rolfbjarne wants to merge 1 commit into
dev/rolf/disposerfrom
dev/rolf/preserve-all-members

Conversation

@rolfbjarne

Copy link
Copy Markdown
Member

When the assembly preparer applies [Preserve (AllMembers = true)] (and when
it preserves NSObject subclasses in user assemblies), it used to emit a single
[DynamicDependency (DynamicallyAccessedMemberTypes.…, typeof (TheType))]
attribute on the module constructor.

That's not equivalent to [Preserve (AllMembers = true)]: the trimmer resolves
DynamicallyAccessedMemberTypes over the entire type hierarchy, so every member
of every base type is preserved as well. For an NSObject subclass that means all
of NSObject is preserved - and NSObject in turn drags in NSNumber,
NSValue, NSArray, ObjCRuntime.Messaging, System.Drawing.PointF/SizeF/ RectangleF and, transitively, the System.Drawing, System.Drawing.Primitives,
System.ObjectModel and System.ComponentModel.TypeConverter assemblies.

In practice this was triggered by the [Preserve (AllMembers = true)] attribute
on the internal NSObject.NSObject_Disposer class, which has 12 members of its
own but ended up preserving 121 members of NSObject and 8 members of
System.Object.

Emit one DynamicDependency attribute per member declared on the type instead,
which matches [Preserve (AllMembers = true)] semantics exactly.

This only affects the assembly preparer (PrepareAssemblies=true); when our steps
run inside ILLink we use an xml descriptor file, which already had the correct
semantics.

Size impact for tests/dotnet/SizeTestApp/iOS (net11.0, ios-arm64, Release,
CoreCLR + ReadyToRun, trimmable-static + PrepareAssemblies + PostProcessAssemblies):

Configuration App size
Before 11,523,224 B
After 11,380,424 B
Difference -142,800 B (-1.24%)

which brings trimmable-static to within 0.38% of managed-static (11,337,504 B),
down from 1.6%.

Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66

…mbers = true)].

When the assembly preparer applies `[Preserve (AllMembers = true)]` (and when
it preserves NSObject subclasses in user assemblies), it used to emit a single
`[DynamicDependency (DynamicallyAccessedMemberTypes.…, typeof (TheType))]`
attribute on the module constructor.

That's not equivalent to `[Preserve (AllMembers = true)]`: the trimmer resolves
`DynamicallyAccessedMemberTypes` over the entire type hierarchy, so every member
of every base type is preserved as well. For an NSObject subclass that means all
of `NSObject` is preserved - and `NSObject` in turn drags in `NSNumber`,
`NSValue`, `NSArray`, `ObjCRuntime.Messaging`, `System.Drawing.PointF/SizeF/
RectangleF` and, transitively, the `System.Drawing`, `System.Drawing.Primitives`,
`System.ObjectModel` and `System.ComponentModel.TypeConverter` assemblies.

In practice this was triggered by the `[Preserve (AllMembers = true)]` attribute
on the internal `NSObject.NSObject_Disposer` class, which has 12 members of its
own but ended up preserving 121 members of `NSObject` and 8 members of
`System.Object`.

Emit one `DynamicDependency` attribute per member declared on the type instead,
which matches `[Preserve (AllMembers = true)]` semantics exactly.

This only affects the assembly preparer (PrepareAssemblies=true); when our steps
run inside ILLink we use an xml descriptor file, which already had the correct
semantics.

Size impact for tests/dotnet/SizeTestApp/iOS (net11.0, ios-arm64, Release,
CoreCLR + ReadyToRun, trimmable-static + PrepareAssemblies + PostProcessAssemblies):

| Configuration                       | App size     |
|-------------------------------------|--------------|
| Before                              | 11,523,224 B |
| After                               | 11,380,424 B |
| **Difference**                      | **-142,800 B (-1.24%)** |

which brings trimmable-static to within 0.38% of managed-static (11,337,504 B),
down from 1.6%.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66
Copilot AI review requested due to automatic review settings July 30, 2026 10:07

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.

Pull request overview

This PR adjusts the dotnet-linker assembly preparer behavior so [Preserve (AllMembers = true)] no longer over-preserves members from base types via a single DynamicDependency(DynamicallyAccessedMemberTypes…, typeof(T)). Instead, it emits one DynamicDependency per member declared on the preserved type, matching Preserve semantics more closely and reducing app size.

Changes:

  • Switch Preserve-all-members logic in the assembly preparer to emit per-member DynamicDependency attributes.
  • Keep the “constructors only” behavior for non-AllMembers preservation.
  • Introduce a shared helper (AddPreserveAllMembersDynamicDependencyAttributes) in AppBundleRewriter.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tools/dotnet-linker/MarkNSObjectsStep.cs Uses the new per-member preservation helper when allMembers == true.
tools/dotnet-linker/ApplyPreserveAttributeStep.cs Uses the new helper for [Preserve(AllMembers=true)] and keeps enum handling as fields-only.
tools/dotnet-linker/AppBundleRewriter.cs Adds the helper that expands preserve-all-members into per-member DynamicDependency attributes.

Comment on lines +1602 to +1603
if (signatures.Count == 0)
return AddAttributeOnlyOnce (addToMethod, CreateDynamicDependencyAttribute (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, type));
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@rolfbjarne
rolfbjarne changed the base branch from main to dev/rolf/disposer July 31, 2026 14:18
@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🔥 [PR Build #c937fde] Build failed (Build macOS tests) 🔥

Build failed for the job 'Build macOS tests' (with job status 'Failed')

Pipeline on Agent
Hash: c937fdeef0829c8d706ad3c28400d5be900b0296 [PR build]

@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: c937fdeef0829c8d706ad3c28400d5be900b0296 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🔥 [CI Build #c937fde] Test results 🔥

Test results

❌ Tests failed on VSTS: test results

5 tests crashed, 68 tests failed, 110 tests passed.

Failures

❌ dotnettests tests (iOS)

1 tests failed, 0 tests passed.

Failed tests

  • DotNet tests: Failed (Execution failed with exit code 1)
    • Xamarin.Tests.AppIconTest.AlternateAppIcon_Failure(iOS,"ios-arm6...: Failure when comparing error messages:
      Unexpected error message #0:
      Expected: Can't find the AlternateAppIcon 'InexistentAppI...
    • Xamarin.Tests.AppIconTest.AlternateAppIcon(iOS,"ios-arm64"): 'dotnet build' failed with exit code 1
      Full command: /Users/cloudtest/vss/_work/1/s/macios/builds/downloads/dotnet-sdk-10.0.400-...
    • Xamarin.Tests.AppIconTest.AlternateAppIcons(iOS,"ios-arm64"): 'dotnet build' failed with exit code 1
      Full command: /Users/cloudtest/vss/_work/1/s/macios/builds/downloads/dotnet-sdk-10.0.400-...
    • ... and 63 more

Html Report (VSDrops) Download

❌ dotnettests tests (MacCatalyst)

1 tests failed, 0 tests passed.

Failed tests

  • DotNet tests: Failed (Execution failed with exit code 1)
    • Xamarin.Tests.AppSizeTest.NativeAOT_TrimmableStatic(MacCatalyst,...: App size changed significantly (-33,264 bytes (-32.5 KB = -0.0 MB) different > tolerance of +-10,240 bytes (10.0 KB = 0.0 MB)). ...
    • Xamarin.Tests.AppSizeTest.NativeAOT(MacCatalyst,"maccatalyst-arm...: App size changed significantly (-183,079 bytes (-178.8 KB = -0.2 MB) different > tolerance of +-10,240 bytes (10.0 KB = 0.0 MB))...
    • Xamarin.Tests.DotNetProjectTest.PublishAotMonoTouchTest_NoIL2009...: 'dotnet build' failed with exit code 1
      Full command: /Users/cloudtest/vss/_work/1/s/macios/builds/downloads/dotnet-sdk-10.0.400-...

Html Report (VSDrops) Download

❌ dotnettests tests (macOS)

1 tests failed, 0 tests passed.

Failed tests

  • DotNet tests: Failed (Execution failed with exit code 1)
    • Xamarin.Tests.AppSizeTest.NativeAOT_TrimmableStatic(MacOSX,"osx-...: App size changed significantly (-16,471 bytes (-16.1 KB = -0.0 MB) different > tolerance of +-10,240 bytes (10.0 KB = 0.0 MB)). ...
    • Xamarin.Tests.AppSizeTest.NativeAOT(MacOSX,"osx-arm64;osx-x64"): App size changed significantly (-379,706 bytes (-370.8 KB = -0.4 MB) different > tolerance of +-10,240 bytes (10.0 KB = 0.0 MB))...
    • Xamarin.Tests.DotNetProjectTest.PublishAotMonoTouchTest_NoIL2009...: 'dotnet build' failed with exit code 1
      Full command: /Users/cloudtest/vss/_work/1/s/macios/builds/downloads/dotnet-sdk-10.0.400-...
    • ... and 1 more

Html Report (VSDrops) Download

❌ dotnettests tests (tvOS)

1 tests failed, 0 tests passed.

Failed tests

  • DotNet tests: Failed (Execution failed with exit code 1)
    • Xamarin.Tests.AppIconTest.AlternateAppIcon_Failure(TVOS,"tvos-ar...: Failure when comparing error messages:
      Unexpected error message #0:
      Expected: Can't find the AlternateAppIcon 'InexistentAppI...
    • Xamarin.Tests.AppIconTest.AlternateAppIcon(TVOS,"tvos-arm64"): 'dotnet build' failed with exit code 1
      Full command: /Users/cloudtest/vss/_work/1/s/macios/builds/downloads/dotnet-sdk-10.0.400-...
    • Xamarin.Tests.AppIconTest.AlternateAppIcons(TVOS,"tvos-arm64"): 'dotnet build' failed with exit code 1
      Full command: /Users/cloudtest/vss/_work/1/s/macios/builds/downloads/dotnet-sdk-10.0.400-...
    • ... and 31 more

Html Report (VSDrops) Download

❌ linker tests (iOS)

5 tests failed, 10 tests passed.

Failed tests

  • link all/iOS - simulator/Debug: BuildFailure
  • link all/iOS - simulator/Release: BuildFailure
  • link all/iOS - simulator/Debug (don't bundle original resources): BuildFailure
  • trimmode link/iOS - simulator/Debug: BuildFailure
  • trimmode link/iOS - simulator/Release: BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))

Html Report (VSDrops) Download

❌ linker tests (MacCatalyst)

5 tests failed, 10 tests passed.

Failed tests

  • link all/Mac Catalyst/Debug: BuildFailure
  • link all/Mac Catalyst/Debug (don't bundle original resources): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • link all/Mac Catalyst/Release: BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • trimmode link/Mac Catalyst/Debug: BuildFailure
  • trimmode link/Mac Catalyst/Release: BuildFailure

Html Report (VSDrops) Download

❌ linker tests (macOS)

2 tests failed, 19 tests passed.

Failed tests

  • link all/macOS/Debug (PrepareAssemblies, CoreCLR, Trimmable Static Registrar): BuildFailure
  • link all/macOS/Release (PrepareAssemblies, CoreCLR, Trimmable Static Registrar): BuildFailure

Html Report (VSDrops) Download

❌ monotouch tests (iOS)

19 tests failed, 0 tests passed.

Failed tests

  • monotouch-test/iOS - simulator/Debug: BuildFailure
  • monotouch-test/iOS - simulator/Release (link sdk): BuildFailure
  • monotouch-test/iOS - simulator/Release (link all): BuildFailure
  • monotouch-test/iOS - simulator/Debug (PrepareAssemblies): BuildFailure
  • monotouch-test/iOS - simulator/Debug (PrepareAssemblies, inline dlfcn, dont link): BuildFailure
  • monotouch-test/iOS - simulator/Debug (LinkSdk): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/iOS - simulator/Debug (static registrar): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/iOS - simulator/Release (all optimizations): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/iOS - simulator/Release (NativeAOT): BuildFailure
  • monotouch-test/iOS - simulator/Release (trimmable static registrar, NativeAOT): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/iOS - simulator/Debug (managed static registrar): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/iOS - simulator/Release (managed static registrar, all optimizations): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/iOS - simulator/Debug (interpreter): BuildFailure
  • monotouch-test/iOS - simulator/Release (interpreter): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/iOS - simulator/Release (compat inline Class.GetHandle): BuildFailure
  • monotouch-test/iOS - simulator/Release (strict inline Class.GetHandle): BuildFailure
  • monotouch-test/iOS - simulator/Release (compat inline dlfcn): BuildFailure
  • monotouch-test/iOS - simulator/Release (strict inline dlfcn, link sdk): BuildFailure
  • monotouch-test/iOS - simulator/Release (NativeAOT, .NET 11 defaults): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))

Html Report (VSDrops) Download

❌ monotouch tests (MacCatalyst)

6 tests failed, 12 tests passed.

Failed tests

  • monotouch-test/Mac Catalyst/Release (link all): BuildFailure
  • monotouch-test/Mac Catalyst/Release (managed static registrar, all optimizations): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/Mac Catalyst/Release (NativeAOT): BuildFailure
  • monotouch-test/Mac Catalyst/Release (NativeAOT, .NET 11 defaults): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/Mac Catalyst/Release (trimmable static registrar, NativeAOT): BuildFailure
  • monotouch-test/Mac Catalyst/Release (static registrar, all optimizations): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))

Html Report (VSDrops) Download

❌ monotouch tests (macOS)

7 tests failed, 12 tests passed.

Failed tests

  • monotouch-test/macOS/Release (link all): BuildFailure
  • monotouch-test/macOS/Release (managed static registrar, all optimizations): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/macOS/Release (trimmable static registrar, all optimizations): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/macOS/Release (NativeAOT): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/macOS/Release (NativeAOT, .NET 11 defaults): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/macOS/Release (trimmable static registrar, NativeAOT): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/macOS/Release (static registrar, all optimizations): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))

Html Report (VSDrops) Download

❌ monotouch tests (tvOS)

19 tests failed, 0 tests passed.

Failed tests

  • monotouch-test/tvOS - simulator/Debug: BuildFailure
  • monotouch-test/tvOS - simulator/Release (link sdk): BuildFailure
  • monotouch-test/tvOS - simulator/Release (link all): BuildFailure
  • monotouch-test/tvOS - simulator/Debug (PrepareAssemblies): BuildFailure
  • monotouch-test/tvOS - simulator/Debug (PrepareAssemblies, inline dlfcn, dont link): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/tvOS - simulator/Debug (LinkSdk): BuildFailure
  • monotouch-test/tvOS - simulator/Debug (static registrar): BuildFailure
  • monotouch-test/tvOS - simulator/Release (all optimizations): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/tvOS - simulator/Release (NativeAOT): BuildFailure
  • monotouch-test/tvOS - simulator/Release (trimmable static registrar, NativeAOT): BuildFailure
  • monotouch-test/tvOS - simulator/Debug (managed static registrar): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/tvOS - simulator/Release (managed static registrar, all optimizations): BuildFailure
  • monotouch-test/tvOS - simulator/Debug (interpreter): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/tvOS - simulator/Release (interpreter): BuildFailure
  • monotouch-test/tvOS - simulator/Release (compat inline Class.GetHandle): BuildFailure
  • monotouch-test/tvOS - simulator/Release (strict inline Class.GetHandle): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/tvOS - simulator/Release (compat inline dlfcn): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/tvOS - simulator/Release (strict inline dlfcn, link sdk): BuildFailure ( (failed to parse the logs: The Writer is closed or in error state.))
  • monotouch-test/tvOS - simulator/Release (NativeAOT, .NET 11 defaults): BuildFailure

Html Report (VSDrops) Download

❌ msbuild tests

1 tests failed, 1 tests passed.

Failed tests

  • MSBuild tasks tests: Failed (Execution failed with exit code 1)
    • Xamarin.MacDev.Tasks.DetectSigningIdentityTaskTests.CustomEntitl...: #RunTask-ErrorCount
      : error: No valid iOS code signing keys found in keychain. You need to request a codesigning certificate ...
    • Xamarin.MacDev.Tasks.DetectSigningIdentityTaskTests.EmptyEntitle...: #RunTask-ErrorCount
      : error: No valid iOS code signing keys found in keychain. You need to request a codesigning certificate ...
    • Xamarin.MacDev.Tasks.DetectSigningIdentityTaskTests.EmptyEntitle...: #RunTask-ErrorCount
      : error: No valid iOS code signing keys found in keychain. You need to request a codesigning certificate ...
    • ... and 7 more

Html Report (VSDrops) Download

❌ Tests on macOS Monterey (12) tests

⚠️ Tests did not run because the Build macOS tests job failed.

Html Report (VSDrops) Download

❌ Tests on macOS Ventura (13) tests

⚠️ Tests did not run because the Build macOS tests job failed.

Html Report (VSDrops) Download

❌ Tests on macOS Sonoma (14) tests

⚠️ Tests did not run because the Build macOS tests job failed.

Html Report (VSDrops) Download

❌ Tests on macOS Sequoia (15) tests

⚠️ Tests did not run because the Build macOS tests job failed.

Html Report (VSDrops) Download

❌ Tests on macOS Tahoe (26) tests

⚠️ Tests did not run because the Build macOS tests job failed.

Html Report (VSDrops) Download

Successes

✅ assembly-processing: All 1 tests passed. Html Report (VSDrops) Download
✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): 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 4 tests passed. Html Report (VSDrops) Download
✅ linker (tvOS): All 15 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

Linux Build Verification

Linux build succeeded

Pipeline on Agent
Hash: c937fdeef0829c8d706ad3c28400d5be900b0296 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

⚠️ AppSizeTest expected files changed ⚠️

The AppSizeTest detected changes in the expected app size files.

To update the expected files, add a comment with the following command:

/apply-gist https://gist.github.com/vs-mobiletools-engineering-service2/61cf0b3adc3f3251b771207b017fbcd4
Updated files
  • iOS-NativeAOT-size.txt
  • iOS-NativeAOT-TrimmableStatic-size.txt
  • MacCatalyst-NativeAOT-size.txt
  • MacCatalyst-NativeAOT-TrimmableStatic-size.txt
  • MacOSX-NativeAOT-size.txt
  • MacOSX-NativeAOT-TrimmableStatic-size.txt
  • TVOS-NativeAOT-size.txt
  • TVOS-NativeAOT-TrimmableStatic-size.txt

Pipeline on Agent
Hash: c937fdeef0829c8d706ad3c28400d5be900b0296 [PR build]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants