ComInterfaceGenerator emits code that the updated memory safety rules reject
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
`Microsoft.Interop.ComInterfaceGenerator` emits `unsafe` on type declarations, and on explicit implementations of members inherited from a base COM interface. Both are errors under the [updated memory safety model](https://learn.microsoft.com/dotnet/csharp/language-reference/unsafe-code#the-updated-memory-safety-model-preview) that is in preview in C# 15 / .NET 11, so any project that opts into `updated-memory-safety-rules` and uses `[GeneratedComInterface]` fails to build.
Because the generated interface is the other half of the user's `partial` type, CS9377 is also reported **on the user's own declaration**, where there is no `unsafe` keyword to remove. That makes the failure look like a bug in the consuming project.
There are two independent problems. The first affects any `[GeneratedComInterface]`; the second needs interface inheritance.
## Configuration
```xml
net11.0
preview
true
$(Features);updated-memory-safety-rules
```
- SDK: `11.0.100-rc.1.26425.128`
- Generator: `Microsoft.Interop.ComInterfaceGenerator 11.0.14.42628`
Removing the `` line makes both projects below build with **0 errors**, so the opt-in is the only trigger.
## Problem 1 — CS9377: `unsafe` on a type declaration
The updated rules make `unsafe` on a type declaration an error, because the modifier has no caller to inform. Reproduced by `1-Standalone` in the repro below — a single interface, no inheritance:
```csharp
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComInterface]
[Guid("00000000-0000-0000-C000-000000000046")]
internal partial interface IStandalone
{
unsafe void GetThing(int** value);
}
```
```
[gen] IStandalone.cs(5,20): error CS9377: The 'unsafe' modifier does not have any effect here under the current memory safety rules.
[gen] IStandalone.cs(13,26): error CS9377: (same)
[gen] IStandalone.cs(20,23): error CS9377: (same)
[src] IStandalone.cs(8,28): error CS9377: (same)
```
`[src] IStandalone.cs(8,28)` is the user's `internal partial interface IStandalone` line.
The three generated declarations carrying the modifier are:
```csharp
file unsafe struct InterfaceImplementationVtable
file unsafe sealed class InterfaceInformation : global::System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceType
file unsafe interface InterfaceImplementation : global::IStandalone
```
Under the updated rules the modifier isn't needed on any of them. Declaring a pointer or function-pointer type, taking an address with `&`, `fixed`, and `sizeof` are all allowed in safe code now; only the operations that actually access the pointed-to memory (`*p`, `p->m`, `p[i]`, function-pointer invocation) still require a context, and those live in member bodies, where an inner `unsafe` block scopes them.
## Problem 2 — CS9366: unsafe implementation of an inherited member
Reproduced by `2-Inherited` in the repro below:
```csharp
[GeneratedComInterface]
[Guid("00000000-0000-0000-C000-000000000046")]
internal partial interface IBase
{
unsafe void GetThing(int** value);
}
[GeneratedComInterface]
[Guid("00000000-0000-0000-C000-000000000047")]
internal partial interface IDerived : IBase
{
unsafe void GetOther(int** value);
}
```
On top of the CS9377s:
```
[gen] IDerived.cs(77,34): error CS9366: Unsafe member 'InterfaceImplementation.IDerived.GetThing(int**)'
cannot implement safe member 'IDerived.GetThing(int**)'
```
The generator emits the shim for the inherited member as:
```csharp
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.ComInterfaceGenerator", "11.0.14.42628")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
unsafe void global::IDerived.GetThing(int** value)
{
var(__this, __vtable) = ((global::System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(typeof(global::IDerived));
var __target = ((delegate* unmanaged[MemberFunction] )__vtable[3]);
...
}
```
Note that `IBase.GetThing` *is* declared `unsafe` in the source above, yet the diagnostic reports the member as safe when it is reached through `IDerived`. Adding or removing `unsafe` on the interface member does not change the outcome, so there appears to be no annotation a consumer can write to make this compile — which is why it reads as a generator bug (or possibly a name-resolution issue in how the inherited member's requires-unsafe state is computed) rather than a missing annotation.
`GetOther`, declared directly on `IDerived`, does **not** produce CS9366 — only the inherited `GetThing` does.
### Reproduction Steps
[comgenerator-repro.zip](https://github.com/user-attachments/files/32069082/comgenerator-repro.zip)
```
comgenerator-repro/
global.json # pins the .NET 11 SDK
Directory.Build.props # TFM, LangVersion=preview, AllowUnsafeBlocks, and the Features opt-in
1-Standalone/ # Problem 1 only
2-Inherited/ # Problems 1 and 2
expected-output.txt # the diagnostics as captured, and the 0-error runs without the opt-in
```
```sh
cd 1-Standalone && dotnet build # CS9377 ×4
cd 2-Inherited && dotnet build # CS9377 ×8, CS9366 ×1
# Either project with the opt-in removed: clean.
dotnet build -p:Features= # 0 errors
```
`EmitCompilerGeneratedFiles` is on, so the generator's output is readable under `obj/Debug/net11.0/generated/Microsoft.Interop.ComInterfaceGenerator/`.
### Expected behavior
A project that opts into `updated-memory-safety-rules` and uses `[GeneratedComInterface]` builds without errors.
### Actual behavior
The build fails with CS9377 (for any generated COM interface, including on declarations the consumer wrote) and CS9366 (whenever a generated COM interface derives from another one).
### Regression?
_No response_
### Known Workarounds
_No response_
### Configuration
```
.NET SDK:
Version: 11.0.100-rc.1.26425.128
Commit: 3551975be0
Workload version: 11.0.100-manifests.a6e318ce
MSBuild version: 18.11.0-1.26425.128+3551975be
Runtime Environment:
OS Name: Mac OS X
OS Version: 26.6
OS Platform: Darwin
RID: osx-arm64
Base Path: /usr/local/share/dotnet/sdk/11.0.100-rc.1.26425.128
.NET workloads installed:
There are no installed workloads to display.
Configured to use workload sets when installing new manifests.
No workload sets are installed. Run "dotnet workload restore" to install a workload set.
Host:
Version: 11.0.0-rc.1.26425.128
Architecture: arm64
Commit: 3551975be0
.NET SDKs installed:
8.0.420 [/usr/local/share/dotnet/sdk]
9.0.313 [/usr/local/share/dotnet/sdk]
10.0.202 [/usr/local/share/dotnet/sdk]
10.0.203 [/usr/local/share/dotnet/sdk]
10.0.300 [/usr/local/share/dotnet/sdk]
10.0.301 [/usr/local/share/dotnet/sdk]
10.0.302 [/usr/local/share/dotnet/sdk]
10.0.400 [/usr/local/share/dotnet/sdk]
11.0.100-preview.4.26230.115 [/usr/local/share/dotnet/sdk]
11.0.100-preview.5.26302.115 [/usr/local/share/dotnet/sdk]
11.0.100-preview.6.26359.118 [/usr/local/share/dotnet/sdk]
11.0.100-preview.7.26381.103 [/usr/local/share/dotnet/sdk]
11.0.100-rc.1.26425.128 [/usr/local/share/dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 8.0.26 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 9.0.15 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.6 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.7 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.8 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.9 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.10 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.11 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 11.0.0-preview.4.26230.115 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 11.0.0-preview.5.26302.115 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 11.0.0-preview.6.26359.118 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 11.0.0-preview.7.26381.103 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 11.0.0-rc.1.26425.128 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 8.0.26 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 9.0.15 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.6 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.7 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.8 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.9 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.10 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.11 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 11.0.0-preview.4.26230.115 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 11.0.0-preview.5.26302.115 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 11.0.0-preview.6.26359.118 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 11.0.0-preview.7.26381.103 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 11.0.0-rc.1.26425.128 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Other architectures found:
None
Environment variables:
DOTNET_ROOT [/usr/local/share/dotnet]
global.json file:
/Users/meziantou/temp/comgenerator-repro/global.json
Learn more:
https://aka.ms/dotnet/info
Download .NET:
https://aka.ms/dotnet/download
```
### Other information
[microsoft/CsWin32#1819](https://github.com/microsoft/CsWin32/pull/1819)
Contributor guide
Research direction
Start with the 1-Standalone and 2-Inherited projects in comgenerator-repro, using global.json and Directory.Build.props, then run dotnet build with updated-memory-safety-rules enabled. Inspect the generated files under obj/Debug/net11.0/generated/Microsoft.Interop.ComInterfaceGenerator/ and compare the CS9377 and CS9366 diagnostics. Done means both projects build with zero errors while the feature remains enabled.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100