dotnet / dotnet/runtime

ComInterfaceGenerator emits code that the updated memory safety rules reject

Open
#133,616 2 comments 0 reactions 0 assignees View on GitHub
area-Interop-coreclr untriaged
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.