Skip to content

[msbuild] Don't turn missing Objective-C classes into hard link errors when inlining Class.GetHandle. Fixes #26268 - #26302

Merged
rolfbjarne merged 3 commits into
net11.0from
dev/rolf/issue-26268
Jul 28, 2026
Merged

[msbuild] Don't turn missing Objective-C classes into hard link errors when inlining Class.GetHandle. Fixes #26268#26302
rolfbjarne merged 3 commits into
net11.0from
dev/rolf/issue-26268

Conversation

@rolfbjarne

@rolfbjarne rolfbjarne commented Jul 24, 2026

Copy link
Copy Markdown
Member

The Class.GetHandle inlining feature (compatibility/strict InlineClassGetHandle)
rewrites a surviving Class.GetHandle ("X") call into a direct native reference
([X class] -> _OBJC_CLASS_$_X). If the native Objective-C class doesn't actually
exist in any linked library, the native link fails with
Undefined symbols: _OBJC_CLASS_$_X, even if the class is never used at runtime.

This happens for third-party bindings that declare a [BaseType (typeof (NSObject))]
type for something that is a protocol - and not a class - natively (the reported
Drastic.FFMpegKit Session type is one example). bgen generates a [Protocol]
attribute on the concrete class it emits for these types, and the static registrar
never registers such classes, so there's no native Objective-C class to reference.

Fix: don't inline Class.GetHandle when the target class has a [Protocol] attribute
(ObjCType.IsFakeProtocol). The call falls back to a runtime lookup instead, which
returns a zero handle (IntPtr.Zero) for a missing native class - the same behavior these
bindings had before Class.GetHandle inlining existed. The change is contained within
InlineClassGetHandleStep.

This replaces an earlier, more complex approach (emitting linker -U flags plus an
objc_getClass runtime fallback), which is reverted in this PR.

The ReferenceNativeSymbol ... Ignore workarounds for the P2 and
ProtocolWithBlockProperties test bindings are now redundant (they're fake protocols,
handled automatically) and are removed; TestProtocolRegister keeps its workaround
since it's a plain [Register] class, not a [Protocol].

Fixes #26268

🤖 Pull request created by Copilot

…s when inlining Class.GetHandle.

The Class.GetHandle inlining feature (compatibility/strict InlineClassGetHandle)
emits a direct native reference ([X class] -> _OBJC_CLASS_$_X) for every surviving
Class.GetHandle call. If the native Objective-C class doesn't actually exist in any
linked library, the native link fails with 'Undefined symbols: _OBJC_CLASS_$_X',
even if the class is never used at runtime.

This happens for third-party bindings that declare a [BaseType (typeof (NSObject))]
type for something that is a protocol - and not a class - natively (the reported
Drastic.FFMpegKit 'Session' type is one example). The existing __attribute__((weak_import))
on the generated @interface isn't enough: ld still errors on a weak-external undefined
symbol that isn't defined in any linked library.

Fix: only emit a direct native reference for classes we can prove exist (classes that
belong to a known platform/SDK framework). For any other class (a third-party binding
class, or a class with no managed type) we:

* pass '-U _OBJC_CLASS_$_X' to the native linker so a missing class doesn't break the
  link (this is a no-op for classes that do exist), and
* fall back to objc_getClass at runtime if the direct reference is null.

This mirrors the weak + dlsym fallback we already do for inlined dlfcn symbols, and
restores the net10 behavior (a missing class handle resolves to null) for these classes
while keeping the fast direct reference + native dead-strip protection for SDK classes.

Fixes #26268

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 07efe22e-a3b1-444f-8ae9-08325ffcaf1c
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@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/846905e9f285759856839e99766b48ac
Updated files
  • MacCatalyst-NativeAOT-TrimmableStatic-size.txt
  • MacOSX-NativeAOT-TrimmableStatic-size.txt
  • TVOS-NativeAOT-TrimmableStatic-size.txt

Pipeline on Agent
Hash: 93d3faa0f41ddafea5525c9a6b8a23b0283fc93c [PR build]

…ttribute.

The new InlineClassGetHandle optimization rewrites `Class.GetHandle ("X")`
into a direct native reference to `_OBJC_CLASS_$_X`. This breaks third-party
bindings that declare a `[BaseType (typeof (NSObject))]` type for something
that is a protocol - not a class - natively (e.g. Drastic.FFMpegKit's
`Session`). bgen generates a `[Protocol]` attribute on the concrete class it
emits for these, and the static registrar never registers such classes, so
there's no native Objective-C class to reference. Inlining the call then turns
into a hard link error (`Undefined symbols: _OBJC_CLASS_$_X`), even when the
class is never used at runtime.

Fix this by skipping the inlining when the target class has a `[Protocol]`
attribute (`ObjCType.IsFakeProtocol`). The call falls back to a runtime lookup
instead, which returns null for a missing native class - the same behavior
these bindings had before Class.GetHandle inlining existed.

This replaces the previous, more complex approach (emitting linker `-U` flags
plus an `objc_getClass` fallback), which is reverted here.

The `ReferenceNativeSymbol ... Ignore` workarounds for the P2 and
ProtocolWithBlockProperties test bindings are now redundant (they're fake
protocols, handled automatically) and are removed; TestProtocolRegister keeps
its workaround since it's a plain `[Register]` class, not a `[Protocol]`.

Fixes #26268

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 07efe22e-a3b1-444f-8ae9-08325ffcaf1c
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

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 InlineClassGetHandle linker optimization to avoid generating hard native link dependencies (_OBJC_CLASS_$_X) for managed “fake protocol” types (classes with a [Protocol] attribute) where no native Objective-C class symbol exists, restoring the previous runtime-lookup behavior and preventing publish-time link failures (fixes #26268).

Changes:

  • Skip Class.GetHandle inlining when the resolved target ObjCType is IsFakeProtocol.
  • Remove now-redundant ReferenceNativeSymbol ... Ignore entries for fake-protocol test bindings in linker test projects.
  • Document the fake-protocol scenario and the new inlining exception in docs/code/class-handles.md.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs Avoids inlining Class.GetHandle for fake-protocol types to prevent undefined ObjC class symbol link errors.
tests/linker/trimmode link/dotnet/shared.csproj Removes redundant ReferenceNativeSymbol ignores for fake-protocol types; keeps the explicit ignore for TestProtocolRegister.
tests/linker/link all/dotnet/shared.csproj Same as above for the “link all” linker test configuration.
docs/code/class-handles.md Documents why inlining must be avoided when the native ObjC class does not exist (fake protocols).

Comment thread tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs
Comment thread docs/code/class-handles.md Outdated
@rolfbjarne
rolfbjarne marked this pull request as ready for review July 27, 2026 18:10
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

Address review feedback: Class.GetHandle returns a NativeHandle (a value
type), so for a missing native class it returns a zero handle (IntPtr.Zero),
not null. Fix the wording in the code comment and the documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 07efe22e-a3b1-444f-8ae9-08325ffcaf1c
@rolfbjarne rolfbjarne added the ready-to-review This PR is ready to review/merge. label Jul 28, 2026
@rolfbjarne
rolfbjarne enabled auto-merge (squash) July 28, 2026 09:50
@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: 75aa7eed5b61d1a57decf4e46d7b0315d63dbffd [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🚀 [CI Build #75aa7ee] Test results 🚀

Test results

✅ All tests passed on VSTS: test results.

🎉 All 256 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 23 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 23 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 18 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 23 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

Linux Build Verification

Linux build succeeded

Pipeline on Agent
Hash: 75aa7eed5b61d1a57decf4e46d7b0315d63dbffd [PR build]

@rolfbjarne
rolfbjarne merged commit a8f6e4e into net11.0 Jul 28, 2026
54 checks passed
@rolfbjarne
rolfbjarne deleted the dev/rolf/issue-26268 branch July 28, 2026 12:20
rolfbjarne added a commit that referenced this pull request Jul 30, 2026
…classes into hard link errors when inlining Class.GetHandle. Fixes #26268 (#26347)

The `Class.GetHandle` inlining feature (compatibility/strict `InlineClassGetHandle`) rewrites a surviving `Class.GetHandle ("X")` call into a direct native reference (`[X class]` -> `_OBJC_CLASS_$_X`). If the native Objective-C class doesn't actually exist in any linked library, the native link fails with `Undefined symbols: _OBJC_CLASS_$_X`, even if the class is never used at runtime.

This happens for third-party bindings that declare a `[BaseType (typeof (NSObject))]` type for something that is a protocol - and not a class - natively (the reported Drastic.FFMpegKit `Session` type is one example). bgen generates a `[Protocol]` attribute on the concrete class it emits for these types, and the static registrar never registers such classes, so there's no native Objective-C class to reference.

Fix: don't inline `Class.GetHandle` when the target class has a `[Protocol]` attribute (`ObjCType.IsFakeProtocol`). The call falls back to a runtime lookup instead, which returns a zero handle (`IntPtr.Zero`) for a missing native class - the same behavior these bindings had before `Class.GetHandle` inlining existed. The change is contained within `InlineClassGetHandleStep`.

The `ReferenceNativeSymbol ... Ignore` workarounds for the `P2` and `ProtocolWithBlockProperties` test bindings are now redundant (they're fake protocols, handled automatically) and are removed; `TestProtocolRegister` keeps its workaround since it's a plain `[Register]` class, not a `[Protocol]`.

Fixes #26268

🤖 Pull request created by Copilot

---------

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

Backport of #26302.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

4 participants