Skip to content

Commit 4254fa3

Browse files
authored
[DllImportGenerator] Don't flag methods with known unsupported UnmanagedType (#61808)
1 parent c159108 commit 4254fa3

File tree

3 files changed

+76
-7
lines changed

3 files changed

+76
-7
lines changed

docs/design/libraries/DllImportGenerator/Compatibility.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ These are all part of `NetCoreApp` and will be referenced by default unless [imp
3333

3434
Marshalling of `char` will not be supported when configured with any of the following:
3535
- [`CharSet.Ansi`, `CharSet.Auto`, or `CharSet.None`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.charset) will not be supported.
36-
- [`UnmangedType.U1` or `UnmangedType.I1`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedtype)
36+
- [`UnmanagedType.U1` or `UnmanagedType.I1`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedtype)
3737
- No explicit marshalling information - either [`DllImportAttribute.CharSet`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.charset) or [`MarshalAsAttribute`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute)
3838

3939
For `CharSet.Ansi` and `CharSet.None`, the built-in system used the [system default Windows ANSI code page](https://docs.microsoft.com/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte) when on Windows and took the first byte of the UTF-8 encoding on non-Windows platforms. The above reasoning also applies to marshalling of a `char` as `UnmanagedType.U1` and `UnmanagedType.I1`. All approaches are fundamentally flawed and therefore not supported. If a single-byte character is expected to be marshalled it is left to the caller to convert a .NET `char` into a single `byte` prior to calling the native function.
@@ -44,7 +44,7 @@ For `CharSet.Auto`, the built-in system relied upon detection at runtime of the
4444

4545
Marshalling of `string` will not be supported when configured with any of the following:
4646
- [`CharSet.None`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.charset)
47-
- [`UnmangedType.VBByRefStr`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedtype)
47+
- [`UnmanagedType.VBByRefStr`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedtype)
4848
- No explicit marshalling information - either [`DllImportAttribute.CharSet`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.charset) or [`MarshalAsAttribute`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute)
4949

5050
When converting from native to managed, the built-in system would throw a [`MarshalDirectiveException`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshaldirectiveexception) if the string's length is over 0x7ffffff0. The generated marshalling code will no longer perform this check.
@@ -63,7 +63,7 @@ Using a custom marshaller (i.e. [`ICustomMarshaler`](https://docs.microsoft.com/
6363

6464
### Array marshalling
6565

66-
Marshalling of arrays will not be supported when using [`UnmangedType.SafeArray`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedtype). This implies that the following `MarshalAsAttribute` fields are unsupported: [`SafeArraySubType`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute.safearraysubtype) and [`SafeArrayUserDefinedSubType`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute.safearrayuserdefinedsubtype)
66+
Marshalling of arrays will not be supported when using [`UnmanagedType.SafeArray`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedtype). This implies that the following `MarshalAsAttribute` fields are unsupported: [`SafeArraySubType`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute.safearraysubtype) and [`SafeArrayUserDefinedSubType`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute.safearrayuserdefinedsubtype)
6767

6868
Specifying array-specific marshalling members on the `MarshalAsAttribute` such as [`SizeConst`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute.sizeconst), [`ArraySubType`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute.arraysubtype), and [`SizeParamIndex`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute.sizeparamindex) with non-array `UnmanagedType` types is unsupported.
6969

@@ -96,6 +96,12 @@ Unlike the built-in system, the source generator does not support marshalling fo
9696
- [`HandleRef`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.handleref)
9797
- [`StringBuilder`](https://docs.microsoft.com/dotnet/api/system.text.stringbuilder)
9898

99+
The source generator also does not support marshalling objects using the following [`UnmanagedType`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedtype) values:
100+
- `UnmanagedType.Interface`
101+
- `UnmanagedType.IDispatch`
102+
- `UnmanagedType.IInspectable`
103+
- `UnmanagedType.IUnknown`
104+
99105
## Version 0
100106

101107
This version is the built-in IL Stub generation system that is triggered whenever a method marked with `DllImportAttribute` is invoked.

src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/ConvertToGeneratedDllImportAnalyzer.cs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Collections.Immutable;
6+
using System.Runtime.InteropServices;
67

78
using Microsoft.CodeAnalysis;
89
using Microsoft.CodeAnalysis.Diagnostics;
@@ -48,6 +49,8 @@ public override void Initialize(AnalysisContext context)
4849
if (generatedDllImportAttrType == null)
4950
return;
5051

52+
INamedTypeSymbol? marshalAsAttrType = compilationContext.Compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_MarshalAsAttribute);
53+
5154
var knownUnsupportedTypes = new List<ITypeSymbol>(s_unsupportedTypeNames.Length);
5255
foreach (string typeName in s_unsupportedTypeNames)
5356
{
@@ -58,11 +61,11 @@ public override void Initialize(AnalysisContext context)
5861
}
5962
}
6063

61-
compilationContext.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, knownUnsupportedTypes), SymbolKind.Method);
64+
compilationContext.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, knownUnsupportedTypes, marshalAsAttrType), SymbolKind.Method);
6265
});
6366
}
6467

65-
private static void AnalyzeSymbol(SymbolAnalysisContext context, List<ITypeSymbol> knownUnsupportedTypes)
68+
private static void AnalyzeSymbol(SymbolAnalysisContext context, List<ITypeSymbol> knownUnsupportedTypes, INamedTypeSymbol? marshalAsAttrType)
6669
{
6770
var method = (IMethodSymbol)context.Symbol;
6871

@@ -84,17 +87,53 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, List<ITypeSymbo
8487
// Ignore methods with unsupported parameters
8588
foreach (IParameterSymbol parameter in method.Parameters)
8689
{
87-
if (knownUnsupportedTypes.Contains(parameter.Type))
90+
if (knownUnsupportedTypes.Contains(parameter.Type)
91+
|| HasUnsupportedUnmanagedTypeValue(parameter.GetAttributes(), marshalAsAttrType))
8892
{
8993
return;
9094
}
9195
}
9296

9397
// Ignore methods with unsupported returns
94-
if (method.ReturnsByRef || method.ReturnsByRefReadonly || knownUnsupportedTypes.Contains(method.ReturnType))
98+
if (method.ReturnsByRef || method.ReturnsByRefReadonly)
99+
return;
100+
101+
if (knownUnsupportedTypes.Contains(method.ReturnType) || HasUnsupportedUnmanagedTypeValue(method.GetReturnTypeAttributes(), marshalAsAttrType))
95102
return;
96103

97104
context.ReportDiagnostic(method.CreateDiagnostic(ConvertToGeneratedDllImport, method.Name));
98105
}
106+
107+
private static bool HasUnsupportedUnmanagedTypeValue(ImmutableArray<AttributeData> attributes, INamedTypeSymbol? marshalAsAttrType)
108+
{
109+
if (marshalAsAttrType == null)
110+
return false;
111+
112+
AttributeData? marshalAsAttr = null;
113+
foreach (AttributeData attr in attributes)
114+
{
115+
if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, marshalAsAttrType))
116+
{
117+
marshalAsAttr = attr;
118+
break;
119+
}
120+
}
121+
122+
if (marshalAsAttr == null || marshalAsAttr.ConstructorArguments.IsEmpty)
123+
return false;
124+
125+
object unmanagedTypeObj = marshalAsAttr.ConstructorArguments[0].Value!;
126+
UnmanagedType unmanagedType = unmanagedTypeObj is short unmanagedTypeAsShort
127+
? (UnmanagedType)unmanagedTypeAsShort
128+
: (UnmanagedType)unmanagedTypeObj;
129+
130+
return !System.Enum.IsDefined(typeof(UnmanagedType), unmanagedType)
131+
|| unmanagedType == UnmanagedType.CustomMarshaler
132+
|| unmanagedType == UnmanagedType.Interface
133+
|| unmanagedType == UnmanagedType.IDispatch
134+
|| unmanagedType == UnmanagedType.IInspectable
135+
|| unmanagedType == UnmanagedType.IUnknown
136+
|| unmanagedType == UnmanagedType.SafeArray;
137+
}
99138
}
100139
}

src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/ConvertToGeneratedDllImportAnalyzerTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Runtime.InteropServices;
67
using System.Threading.Tasks;
78

89
using Microsoft.CodeAnalysis;
@@ -153,6 +154,29 @@ public async Task UnsupportedType_NoDiagnostic(Type type)
153154
await VerifyCS.VerifyAnalyzerAsync(source);
154155
}
155156

157+
[ConditionalTheory]
158+
[InlineData(UnmanagedType.Interface)]
159+
[InlineData(UnmanagedType.IDispatch)]
160+
[InlineData(UnmanagedType.IInspectable)]
161+
[InlineData(UnmanagedType.IUnknown)]
162+
[InlineData(UnmanagedType.SafeArray)]
163+
public async Task UnsupportedUnmanagedType_NoDiagnostic(UnmanagedType unmanagedType)
164+
{
165+
string source = $@"
166+
using System.Runtime.InteropServices;
167+
unsafe partial class Test
168+
{{
169+
[DllImport(""DoesNotExist"")]
170+
public static extern void Method_Parameter([MarshalAs(UnmanagedType.{unmanagedType}, MarshalType = ""DNE"")]int p);
171+
172+
[DllImport(""DoesNotExist"")]
173+
[return: MarshalAs(UnmanagedType.{unmanagedType}, MarshalType = ""DNE"")]
174+
public static extern int Method_Return();
175+
}}
176+
";
177+
await VerifyCS.VerifyAnalyzerAsync(source);
178+
}
179+
156180
[ConditionalFact]
157181
public async Task GeneratedDllImport_NoDiagnostic()
158182
{

0 commit comments

Comments
 (0)