diff options
author | Jeremy Koritzinsky <jekoritz@microsoft.com> | 2020-09-01 20:11:11 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-01 20:11:11 +0300 |
commit | 0f74067286e3fd422da603e0fc7eb46feb0ad8c6 (patch) | |
tree | 3716343c62098d31d596be492ba4040a71da943c /src/libraries/System.Runtime.InteropServices | |
parent | 83fbd4e34d5256a904fa3fa923687b5e67f75a69 (diff) |
Add the struct marshalling attributes to Ancillary.Interop and add an analyzer that validates manual usage. (dotnet/runtimelab#61)
Co-authored-by: Elinor Fung <elfung@microsoft.com>
Commit migrated from https://github.com/dotnet/runtimelab/commit/05d6ef236f7bd698870b17e8abb1b7c6bb130198
Diffstat (limited to 'src/libraries/System.Runtime.InteropServices')
14 files changed, 2104 insertions, 19 deletions
diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/CompileFails.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/CompileFails.cs index 75e8847232d..3acbdace6ce 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/CompileFails.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/CompileFails.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using System.Collections.Generic; +using System.Threading.Tasks; using Xunit; namespace DllImportGenerator.Test @@ -13,9 +14,9 @@ namespace DllImportGenerator.Test [Theory] [MemberData(nameof(CodeSnippetsToCompile))] - public void ValidateSnippets(string source, int failCount) + public async Task ValidateSnippets(string source, int failCount) { - Compilation comp = TestUtils.CreateCompilation(source); + Compilation comp = await TestUtils.CreateCompilation(source); TestUtils.AssertPreSourceGeneratorCompilation(comp); var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Compiles.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Compiles.cs index 85cd5cad87a..9e0fda1aab0 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Compiles.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using System.Collections.Generic; +using System.Threading.Tasks; using Xunit; namespace DllImportGenerator.Test @@ -23,9 +24,9 @@ namespace DllImportGenerator.Test [Theory] [MemberData(nameof(CodeSnippetsToCompile))] - public void ValidateSnippets(string source) + public async Task ValidateSnippets(string source) { - Compilation comp = TestUtils.CreateCompilation(source); + Compilation comp = await TestUtils.CreateCompilation(source); TestUtils.AssertPreSourceGeneratorCompilation(comp); var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/DllImportGenerator.Test.csproj b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/DllImportGenerator.Test.csproj index 4cba239e624..298f48c6786 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/DllImportGenerator.Test.csproj +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/DllImportGenerator.Test.csproj @@ -9,6 +9,7 @@ <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0-3.final" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.0-beta1.final" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.0.1-beta1.20418.1" PrivateAssets="all" /> <PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="3.7.0-3.final"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> @@ -29,5 +30,8 @@ <ProjectReference Include="..\Ancillary.Interop\Ancillary.Interop.csproj" /> <ProjectReference Include="..\DllImportGenerator\DllImportGenerator.csproj" /> </ItemGroup> - + + <PropertyGroup> + <RestoreAdditionalProjectSources>https://dotnet.myget.org/F/roslyn-analyzers/api/v3/index.json ;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources> + </PropertyGroup> </Project> diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/ManualTypeMarshallingAnalyzerTests.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/ManualTypeMarshallingAnalyzerTests.cs new file mode 100644 index 00000000000..414a4bcd091 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/ManualTypeMarshallingAnalyzerTests.cs @@ -0,0 +1,974 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using static Microsoft.Interop.ManualTypeMarshallingAnalyzer; + +using VerifyCS = DllImportGenerator.Test.Verifiers.CSharpAnalyzerVerifier<Microsoft.Interop.ManualTypeMarshallingAnalyzer>; + +namespace DllImportGenerator.Test +{ + public class ManualTypeMarshallingAnalyzerTests + { + public static IEnumerable<object[]> NonBlittableTypeMarkedBlittable_ReportsDiagnostic_TestData { + get + { + yield return new object[] + { + @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ + public bool field; +} +" + }; + yield return new object[] + { + @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ + public char field; +} +" + }; + yield return new object[] + { + +@" +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ + public string field; +} +" + }; + } + } + + [MemberData(nameof(NonBlittableTypeMarkedBlittable_ReportsDiagnostic_TestData))] + [Theory] + public async Task NonBlittableTypeMarkedBlittable_ReportsDiagnostic(string source) + { + var diagnostic = VerifyCS.Diagnostic(BlittableTypeMustBeBlittableRule).WithSpan(4, 2, 4, 15).WithArguments("S"); + await VerifyCS.VerifyAnalyzerAsync(source, diagnostic); + } + + [Fact] + public async Task TypeWithBlittablePrimitiveFieldsMarkedBlittableNoDiagnostic() + { + + string source = @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ + public int field; +} +"; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task TypeWithBlittableStructFieldsMarkedBlittableNoDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ + public T field; +} + +[BlittableType] +struct T +{ + public int field; +} +"; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task TypeMarkedBlittableWithNonBlittableFieldsMarkedBlittableReportDiagnosticOnFieldTypeDefinition() + { + string source = @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ + public T field; +} + +[BlittableType] +struct T +{ + public bool field; +} +"; + var diagnostic = VerifyCS.Diagnostic(BlittableTypeMustBeBlittableRule).WithSpan(10, 2, 10, 15).WithArguments("T"); + await VerifyCS.VerifyAnalyzerAsync(source, diagnostic); + } + + [Fact] + public async Task NonUnmanagedTypeMarkedBlittable_ReportsDiagnosticOnStructType() + { + string source = @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ + public T field; +} + +[BlittableType] +struct T +{ + public string field; +} +"; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(BlittableTypeMustBeBlittableRule).WithSpan(4, 2, 4, 15).WithArguments("S"), + VerifyCS.Diagnostic(BlittableTypeMustBeBlittableRule).WithSpan(10, 2, 10, 15).WithArguments("T")); + } + + [Fact] + public async Task BlittableTypeWithNonBlittableStaticField_DoesNotReportDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ + public static string Static; + public int instance; +} +"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NullNativeType_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[NativeMarshalling(null)] +struct S +{ + public string s; +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeNonNullRule).WithSpan(4, 2, 4, 25).WithArguments("S")); + } + + [Fact] + public async Task NonNamedNativeType_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(int*))] +struct S +{ + public string s; +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustHaveRequiredShapeRule).WithSpan(4, 2, 4, 33).WithArguments("int*", "S")); + } + + [Fact] + public async Task NonBlittableNativeType_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +struct Native +{ + private string value; + + public Native(S s) + { + value = s.s; + } + + public S ToManaged() => new S { s = value }; +}"; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithSpan(10, 1, 20, 2).WithArguments("Native", "S")); + } + + [Fact] + public async Task ClassNativeType_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +class Native +{ + private IntPtr value; + + public Native(S s) + { + } + + public S ToManaged() => new S(); +}"; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithSpan(11, 1, 20, 2).WithArguments("Native", "S")); + } + + [Fact] + public async Task BlittableNativeType_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +[BlittableType] +struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); +}"; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task BlittableNativeWithNonBlittableValueProperty_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +[BlittableType] +struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public string Value { get => null; set {} } +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithSpan(23, 5, 23, 48).WithArguments("string", "S")); + } + + [Fact] + public async Task NonBlittableNativeTypeWithBlittableValueProperty_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +struct Native +{ + private string value; + + public Native(S s) + { + value = s.s; + } + + public S ToManaged() => new S() { s = value }; + + public IntPtr Value { get => IntPtr.Zero; set {} } +}"; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task ClassNativeTypeWithValueProperty_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +class Native +{ + private string value; + + public Native(S s) + { + value = s.s; + } + + public S ToManaged() => new S() { s = value }; + + public IntPtr Value { get => IntPtr.Zero; set {} } +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustHaveRequiredShapeRule).WithSpan(11, 1, 23, 2).WithArguments("Native", "S")); + } + + [Fact] + public async Task NonBlittableGetPinnableReferenceReturnType_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public char c; + + public ref char GetPinnableReference() => ref c; +} + +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public IntPtr Value { get => IntPtr.Zero; set {} } +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(GetPinnableReferenceReturnTypeBlittableRule).WithSpan(10, 5, 10, 53)); + } + + + [Fact] + public async Task BlittableGetPinnableReferenceReturnType_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; + + public ref byte GetPinnableReference() => ref c; +} + +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) : this() + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public IntPtr Value { get => IntPtr.Zero; set {} } +}"; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task TypeWithGetPinnableReferenceNonPointerReturnType_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; + + public ref byte GetPinnableReference() => ref c; +} + +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) : this() + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public int Value { get => 0; set {} } +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBePointerSizedRule).WithSpan(24, 5, 24, 42).WithArguments("int", "S")); + } + + [Fact] + public async Task BlittableValueTypeWithNoFields_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[BlittableType] +struct S +{ +}"; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NativeTypeWithNoMarshallingMethods_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[BlittableType] +struct Native +{ +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustHaveRequiredShapeRule).WithSpan(11, 1, 14, 2).WithArguments("Native", "S")); + } + + [Fact] + public async Task NativeTypeWithOnlyConstructor_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[BlittableType] +struct Native +{ + public Native(S s) {} +}"; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NativeTypeWithOnlyToManagedMethod_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[BlittableType] +struct Native +{ + public S ToManaged() => new S(); +}"; + + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NativeTypeWithOnlyStackallocConstructor_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[BlittableType] +struct Native +{ + public Native(S s, Span<byte> buffer) {} +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(StackallocMarshallingShouldSupportAllocatingMarshallingFallbackRule).WithSpan(11, 1, 15, 2).WithArguments("Native")); + } + + [Fact] + public async Task TypeWithOnlyNativeStackallocConstructorAndGetPinnableReference_ReportsDiagnostics() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; + public ref byte GetPinnableReference() => ref c; +} + +struct Native +{ + public Native(S s, Span<byte> buffer) {} + + public IntPtr Value => IntPtr.Zero; +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(StackallocMarshallingShouldSupportAllocatingMarshallingFallbackRule).WithSpan(12, 1, 17, 2).WithArguments("Native"), + VerifyCS.Diagnostic(GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule).WithSpan(5, 2, 5, 35).WithArguments("S", "Native")); + } + + [Fact] + public async Task NativeTypeWithConstructorAndSetOnlyValueProperty_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +struct Native +{ + public Native(S s) {} + + public IntPtr Value { set {} } +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(ValuePropertyMustHaveGetterRule).WithSpan(15, 5, 15, 35).WithArguments("Native")); + } + + [Fact] + public async Task NativeTypeWithToManagedAndGetOnlyValueProperty_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +struct Native +{ + public S ToManaged() => new S(); + + public IntPtr Value => IntPtr.Zero; +}"; + + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(ValuePropertyMustHaveSetterRule).WithSpan(15, 5, 15, 40).WithArguments("Native")); + } + + [Fact] + public async Task BlittableNativeTypeOnMarshalUsingParameter_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S +{ + public string s; +} + +[BlittableType] +struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); +} + + +static class Test +{ + static void Foo([MarshalUsing(typeof(Native))] S s) + {} +} +"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NonBlittableNativeTypeOnMarshalUsingParameter_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S +{ + public string s; +} + +struct Native +{ + private string value; + + public Native(S s) : this() + { + } + + public S ToManaged() => new S(); +} + + +static class Test +{ + static void Foo([MarshalUsing(typeof(Native))] S s) + {} +} +"; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithSpan(10, 1, 19, 2).WithArguments("Native", "S")); + } + + [Fact] + public async Task NonBlittableNativeTypeOnMarshalUsingReturn_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S +{ + public string s; +} + +struct Native +{ + private string value; + + public Native(S s) : this() + { + } + + public S ToManaged() => new S(); +} + + +static class Test +{ + [return: MarshalUsing(typeof(Native))] + static S Foo() => new S(); +} +"; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithSpan(10, 1, 19, 2).WithArguments("Native", "S")); + } + + [Fact] + public async Task NonBlittableNativeTypeOnMarshalUsingField_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S +{ + public string s; +} + +struct Native +{ + private string value; + + public Native(S s) : this() + { + } + + public S ToManaged() => new S(); +} + + +struct Test +{ + [MarshalUsing(typeof(Native))] + S s; +} +"; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithSpan(10, 1, 19, 2).WithArguments("Native", "S")); + } + + + [Fact] + public async Task GenericNativeTypeWithValueTypeValueProperty_DoesNotReportDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native<S>))] +struct S +{ + public string s; +} + +struct Native<T> + where T : new() +{ + public Native(T s) + { + Value = 0; + } + + public T ToManaged() => new T(); + + public int Value { get; set; } +}"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task GenericNativeTypeWithGenericMemberInstantiatedWithBlittable_DoesNotReportDiagnostic() + { + + string source = @" +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native<int>))] +struct S +{ + public string s; +} + +struct Native<T> + where T : new() +{ + public Native(S s) + { + Value = new T(); + } + + public S ToManaged() => new S(); + + public T Value { get; set; } +}"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + public static IEnumerable<object[]> GenericTypeWithGenericFieldMarkedBlittable_ReportsDiagnostic_TestData { + get + { + yield return new object[] + { + @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S<T> +{ + public T t; +}" + }; + yield return new object[] + { + @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S<T> where T : class +{ + public T t; +}" + }; + yield return new object[] + { + @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S<T> where T : struct +{ + public T t; +}" + }; + yield return new object[] + { + @" +using System.Runtime.InteropServices; + +[BlittableType] +struct S<T> where T : unmanaged +{ + public T t; +}" + }; + } + } + + [MemberData(nameof(GenericTypeWithGenericFieldMarkedBlittable_ReportsDiagnostic_TestData))] + [Theory] + public async Task GenericTypeWithGenericFieldMarkedBlittable_ReportsDiagnostic(string source) + { + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(BlittableTypeMustBeBlittableRule).WithSpan(4, 2, 4, 15).WithArguments("S<T>")); + } + + [Fact] + public async Task ValueTypeContainingPointerBlittableType_DoesNotReportDiagnostic() + { + var source = @" +using System.Runtime.InteropServices; + +[BlittableType] +unsafe struct S +{ + private int* ptr; +}"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task ValueTypeContainingPointerToNonBlittableType_ReportsDiagnostic() + { + var source = @" +using System.Runtime.InteropServices; + +[BlittableType] +unsafe struct S +{ + private bool* ptr; +}"; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(BlittableTypeMustBeBlittableRule).WithSpan(4, 2, 4, 15).WithArguments("S")); + } + + [Fact] + public async Task BlittableValueTypeContainingPointerToSelf_DoesNotReportDiagnostic() + { + + var source = @" +using System.Runtime.InteropServices; + +[BlittableType] +unsafe struct S +{ + private int fld; + private S* ptr; +}"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task NonBlittableValueTypeContainingPointerToSelf_ReportsDiagnostic() + { + var source = @" +using System.Runtime.InteropServices; + +[BlittableType] +unsafe struct S +{ + private bool fld; + private S* ptr; +}"; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(BlittableTypeMustBeBlittableRule).WithSpan(4, 2, 4, 15).WithArguments("S")); + } + + [Fact] + public async Task BlittableTypeContainingFunctionPointer_DoesNotReportDiagnostic() + { + var source = @" +using System.Runtime.InteropServices; + +[BlittableType] +unsafe struct S +{ + private delegate*<int> ptr; +}"; + await VerifyCS.VerifyAnalyzerAsync(source); + } + } +}
\ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/TestUtils.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/TestUtils.cs index a52480f22ea..5e56b9d5aca 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/TestUtils.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/TestUtils.cs @@ -1,10 +1,13 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace DllImportGenerator.Test @@ -35,26 +38,26 @@ namespace DllImportGenerator.Test /// <param name="source">Source to compile</param> /// <param name="outputKind">Output type</param> /// <returns>The resulting compilation</returns> - public static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) + public static async Task<Compilation> CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) { - var mdRefs = new List<MetadataReference>(); + var (mdRefs, ancillary) = GetReferenceAssemblies(); + + return CSharpCompilation.Create("compilation", + new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, + (await mdRefs.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)).Add(ancillary), + new CSharpCompilationOptions(outputKind)); + } + + public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies() + { + // TODO: When .NET 5.0 releases, we can simplify this. + var referenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp50; // Include the assembly containing the new attribute and all of its references. // [TODO] Remove once the attribute has been added to the BCL var attrAssem = typeof(GeneratedDllImportAttribute).GetTypeInfo().Assembly; - mdRefs.Add(MetadataReference.CreateFromFile(attrAssem.Location)); - foreach (var assemName in attrAssem.GetReferencedAssemblies()) - { - var assemRef = Assembly.Load(assemName); - mdRefs.Add(MetadataReference.CreateFromFile(assemRef.Location)); - } - // Add a CoreLib reference - mdRefs.Add(MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location)); - return CSharpCompilation.Create("compilation", - new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, - mdRefs, - new CSharpCompilationOptions(outputKind)); + return (referenceAssemblies, MetadataReference.CreateFromFile(attrAssem.Location)); } /// <summary> diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Verifiers/CSharpAnalyzerVerifier.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Verifiers/CSharpAnalyzerVerifier.cs new file mode 100644 index 00000000000..9ccff97f18b --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Verifiers/CSharpAnalyzerVerifier.cs @@ -0,0 +1,64 @@ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.CSharp.Testing.XUnit; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace DllImportGenerator.Test.Verifiers +{ + public static class CSharpAnalyzerVerifier<TAnalyzer> + where TAnalyzer : DiagnosticAnalyzer, new() + { + /// <inheritdoc cref="AnalyzerVerifier{TAnalyzer}.Diagnostic()"/> + public static DiagnosticResult Diagnostic() + => AnalyzerVerifier<TAnalyzer>.Diagnostic(); + + /// <inheritdoc cref="AnalyzerVerifier{TAnalyzer}.Diagnostic(string)"/> + public static DiagnosticResult Diagnostic(string diagnosticId) + => AnalyzerVerifier<TAnalyzer>.Diagnostic(diagnosticId); + + /// <inheritdoc cref="AnalyzerVerifier{TAnalyzer}.Diagnostic(DiagnosticDescriptor)"/> + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => AnalyzerVerifier<TAnalyzer>.Diagnostic(descriptor); + + /// <inheritdoc cref="AnalyzerVerifier{TAnalyzer}.VerifyAnalyzerAsync(string, DiagnosticResult[])"/> + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + internal class Test : CSharpAnalyzerTest<TAnalyzer, XUnitVerifier> + { + public Test() + { + var (refAssem, ancillary) = TestUtils.GetReferenceAssemblies(); + ReferenceAssemblies = refAssem; + SolutionTransforms.Add((solution, projectId) => + { + var project = solution.GetProject(projectId); + var compilationOptions = project.CompilationOptions; + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + solution = solution.WithProjectMetadataReferences(projectId, project.MetadataReferences.Concat(ImmutableArray.Create(ancillary))); + solution = solution.WithProjectParseOptions(projectId, ((CSharpParseOptions)project.ParseOptions!).WithLanguageVersion(LanguageVersion.Preview)); + + return solution; + }); + } + } + } +}
\ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Verifiers/CSharpVerifierHelper.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Verifiers/CSharpVerifierHelper.cs new file mode 100644 index 00000000000..61c06397527 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator.Test/Verifiers/CSharpVerifierHelper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace DllImportGenerator.Test.Verifiers +{ + internal static class CSharpVerifierHelper + { + /// <summary> + /// By default, the compiler reports diagnostics for nullable reference types at + /// <see cref="DiagnosticSeverity.Warning"/>, and the analyzer test framework defaults to only validating + /// diagnostics at <see cref="DiagnosticSeverity.Error"/>. This map contains all compiler diagnostic IDs + /// related to nullability mapped to <see cref="ReportDiagnostic.Error"/>, which is then used to enable all + /// of these warnings for default validation during analyzer and code fix tests. + /// </summary> + internal static ImmutableDictionary<string, ReportDiagnostic> NullableWarnings { get; } = GetNullableWarningsFromCompiler(); + + private static ImmutableDictionary<string, ReportDiagnostic> GetNullableWarningsFromCompiler() + { + string[] args = { "/warnaserror:nullable" }; + var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory); + var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; + + // Workaround for https://github.com/dotnet/roslyn/issues/41610 + nullableWarnings = nullableWarnings + .SetItem("CS8632", ReportDiagnostic.Error) + .SetItem("CS8669", ReportDiagnostic.Error); + + return nullableWarnings; + } + } +}
\ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.csproj b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.csproj index 8082fd11173..e2eb6d380e8 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.csproj +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.csproj @@ -43,4 +43,19 @@ <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> </ItemGroup> + <ItemGroup> + <Compile Update="Resources.Designer.cs"> + <DesignTime>True</DesignTime> + <AutoGen>True</AutoGen> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Update="Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + </Project> diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/ManualTypeMarshallingAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/ManualTypeMarshallingAnalyzer.cs new file mode 100644 index 00000000000..29860466b76 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/ManualTypeMarshallingAnalyzer.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using DllImportGenerator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +#nullable enable + +namespace Microsoft.Interop +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ManualTypeMarshallingAnalyzer : DiagnosticAnalyzer + { + private const string Category = "Interoperability"; + public readonly static DiagnosticDescriptor BlittableTypeMustBeBlittableRule = + new DiagnosticDescriptor( + "INTEROPGEN001", + "BlittableTypeMustBeBlittable", + new LocalizableResourceString(nameof(Resources.BlittableTypeMustBeBlittableMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.BlittableTypeMustBeBlittableDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor CannotHaveMultipleMarshallingAttributesRule = + new DiagnosticDescriptor( + "INTEROPGEN002", + "CannotHaveMultipleMarshallingAttributes", + new LocalizableResourceString(nameof(Resources.CannotHaveMultipleMarshallingAttributesMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.CannotHaveMultipleMarshallingAttributesDescription), Resources.ResourceManager, typeof(Resources))); + + + public readonly static DiagnosticDescriptor NativeTypeMustBeNonNullRule = + new DiagnosticDescriptor( + "INTEROPGEN003", + "NativeTypeMustBeNonNull", + new LocalizableResourceString(nameof(Resources.NativeTypeMustBeNonNullMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.NativeTypeMustBeNonNullDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor NativeTypeMustBeBlittableRule = + new DiagnosticDescriptor( + "INTEROPGEN004", + "NativeTypeMustBeBlittable", + new LocalizableResourceString(nameof(Resources.NativeTypeMustBeBlittableMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.BlittableTypeMustBeBlittableDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor GetPinnableReferenceReturnTypeBlittableRule = + new DiagnosticDescriptor( + "INTEROPGEN005", + "GetPinnableReferenceReturnTypeBlittable", + new LocalizableResourceString(nameof(Resources.GetPinnableReferenceReturnTypeBlittableMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.GetPinnableReferenceReturnTypeBlittableDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor NativeTypeMustBePointerSizedRule = + new DiagnosticDescriptor( + "INTEROPGEN006", + "NativeTypeMustBePointerSized", + new LocalizableResourceString(nameof(Resources.NativeTypeMustBePointerSizedMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.NativeTypeMustBePointerSizedDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor NativeTypeMustHaveRequiredShapeRule = + new DiagnosticDescriptor( + "INTEROPGEN007", + "NativeTypeMustHaveRequiredShape", + new LocalizableResourceString(nameof(Resources.NativeTypeMustHaveRequiredShapeMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.NativeTypeMustHaveRequiredShapeDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor ValuePropertyMustHaveSetterRule = + new DiagnosticDescriptor( + "INTEROPGEN008", + "ValuePropertyMustHaveSetter", + new LocalizableResourceString(nameof(Resources.ValuePropertyMustHaveSetterMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.ValuePropertyMustHaveSetterDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor ValuePropertyMustHaveGetterRule = + new DiagnosticDescriptor( + "INTEROPGEN009", + "ValuePropertyMustHaveGetter", + new LocalizableResourceString(nameof(Resources.ValuePropertyMustHaveGetterMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.ValuePropertyMustHaveGetterDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule = + new DiagnosticDescriptor( + "INTEROPGEN010", + "GetPinnableReferenceShouldSupportAllocatingMarshallingFallback", + new LocalizableResourceString(nameof(Resources.GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription), Resources.ResourceManager, typeof(Resources))); + + public readonly static DiagnosticDescriptor StackallocMarshallingShouldSupportAllocatingMarshallingFallbackRule = + new DiagnosticDescriptor( + "INTEROPGEN011", + "StackallocMarshallingShouldSupportAllocatingMarshallingFallback", + new LocalizableResourceString(nameof(Resources.StackallocMarshallingShouldSupportAllocatingMarshallingFallbackMessage), Resources.ResourceManager, typeof(Resources)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: new LocalizableResourceString(nameof(Resources.StackallocMarshallingShouldSupportAllocatingMarshallingFallbackDescription), Resources.ResourceManager, typeof(Resources))); + + public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => + ImmutableArray.Create( + BlittableTypeMustBeBlittableRule, + CannotHaveMultipleMarshallingAttributesRule, + NativeTypeMustBeNonNullRule, + NativeTypeMustBeBlittableRule, + GetPinnableReferenceReturnTypeBlittableRule, + NativeTypeMustBePointerSizedRule, + NativeTypeMustHaveRequiredShapeRule, + ValuePropertyMustHaveSetterRule, + ValuePropertyMustHaveGetterRule, + GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule, + StackallocMarshallingShouldSupportAllocatingMarshallingFallbackRule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(PrepareForAnalysis); + } + + private void PrepareForAnalysis(CompilationStartAnalysisContext context) + { + var generatedMarshallingAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.GeneratedMarshallingAttribute); + var blittableTypeAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.BlittableTypeAttribute); + var nativeMarshallingAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.NativeMarshallingAttribute); + var marshalUsingAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.MarshalUsingAttribute); + var spanOfByte = context.Compilation.GetTypeByMetadataName(TypeNames.System_Span)!.Construct(context.Compilation.GetSpecialType(SpecialType.System_Byte)); + + if (generatedMarshallingAttribute is not null && + blittableTypeAttribute is not null && + nativeMarshallingAttribute is not null && + marshalUsingAttribute is not null && + spanOfByte is not null) + { + var perCompilationAnalyzer = new PerCompilationAnalyzer(generatedMarshallingAttribute, blittableTypeAttribute, nativeMarshallingAttribute, marshalUsingAttribute, spanOfByte); + context.RegisterSymbolAction(context => perCompilationAnalyzer.AnalyzeTypeDefinition(context), SymbolKind.NamedType); + context.RegisterSymbolAction(context => perCompilationAnalyzer.AnalyzeElement(context), SymbolKind.Parameter, SymbolKind.Field); + context.RegisterSymbolAction(context => perCompilationAnalyzer.AnalyzeReturnType(context), SymbolKind.Method); + } + } + + class PerCompilationAnalyzer + { + private readonly INamedTypeSymbol GeneratedMarshallingAttribute; + private readonly INamedTypeSymbol BlittableTypeAttribute; + private readonly INamedTypeSymbol NativeMarshallingAttribute; + private readonly INamedTypeSymbol MarshalUsingAttribute; + private readonly ITypeSymbol SpanOfByte; + + public PerCompilationAnalyzer(INamedTypeSymbol generatedMarshallingAttribute, + INamedTypeSymbol blittableTypeAttribute, + INamedTypeSymbol nativeMarshallingAttribute, + INamedTypeSymbol marshalUsingAttribute, + INamedTypeSymbol spanOfByte) + { + GeneratedMarshallingAttribute = generatedMarshallingAttribute; + BlittableTypeAttribute = blittableTypeAttribute; + NativeMarshallingAttribute = nativeMarshallingAttribute; + MarshalUsingAttribute = marshalUsingAttribute; + SpanOfByte = spanOfByte; + } + + public void AnalyzeTypeDefinition(SymbolAnalysisContext context) + { + INamedTypeSymbol type = (INamedTypeSymbol)context.Symbol; + + AttributeData? blittableTypeAttributeData = null; + AttributeData? nativeMarshallingAttributeData = null; + foreach (var attr in type.GetAttributes()) + { + if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, GeneratedMarshallingAttribute)) + { + // If the type has the GeneratedMarshallingAttribute, + // we let the source generator handle error checking. + return; + } + else if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, BlittableTypeAttribute)) + { + blittableTypeAttributeData = attr; + } + else if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, NativeMarshallingAttribute)) + { + nativeMarshallingAttributeData = attr; + } + } + + if (blittableTypeAttributeData is not null && nativeMarshallingAttributeData is not null) + { + context.ReportDiagnostic(Diagnostic.Create(CannotHaveMultipleMarshallingAttributesRule, blittableTypeAttributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(), type.ToDisplayString())); + } + else if (blittableTypeAttributeData is not null && !type.HasOnlyBlittableFields()) + { + context.ReportDiagnostic(Diagnostic.Create(BlittableTypeMustBeBlittableRule, blittableTypeAttributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(), type.ToDisplayString())); + } + else if (nativeMarshallingAttributeData is not null) + { + AnalyzeNativeMarshalerType(context, type, nativeMarshallingAttributeData, validateGetPinnableReference: true, validateAllScenarioSupport: true); + } + } + + public void AnalyzeElement(SymbolAnalysisContext context) + { + AttributeData? attrData = context.Symbol.GetAttributes().FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(MarshalUsingAttribute, attr.AttributeClass)); + if (attrData is not null) + { + if (context.Symbol is IParameterSymbol param) + { + AnalyzeNativeMarshalerType(context, param.Type, attrData, false, false); + } + else if (context.Symbol is IFieldSymbol field) + { + AnalyzeNativeMarshalerType(context, field.Type, attrData, false, false); + } + } + } + + public void AnalyzeReturnType(SymbolAnalysisContext context) + { + var method = (IMethodSymbol)context.Symbol; + AttributeData? attrData = method.GetReturnTypeAttributes().FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(MarshalUsingAttribute, attr.AttributeClass)); + if (attrData is not null) + { + AnalyzeNativeMarshalerType(context, method.ReturnType, attrData, false, false); + } + } + + private void AnalyzeNativeMarshalerType(SymbolAnalysisContext context, ITypeSymbol type, AttributeData nativeMarshalerAttributeData, bool validateGetPinnableReference, bool validateAllScenarioSupport) + { + if (nativeMarshalerAttributeData.ConstructorArguments[0].IsNull) + { + context.ReportDiagnostic(Diagnostic.Create(NativeTypeMustBeNonNullRule, nativeMarshalerAttributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(), type.ToDisplayString())); + return; + } + + ITypeSymbol nativeType = (ITypeSymbol)nativeMarshalerAttributeData.ConstructorArguments[0].Value!; + + if (!nativeType.IsValueType) + { + context.ReportDiagnostic(Diagnostic.Create(NativeTypeMustHaveRequiredShapeRule, GetSyntaxReferenceForDiagnostic(nativeType).GetSyntax().GetLocation(), nativeType.ToDisplayString(), type.ToDisplayString())); + return; + } + + if (nativeType is not INamedTypeSymbol marshalerType) + { + context.ReportDiagnostic(Diagnostic.Create(NativeTypeMustHaveRequiredShapeRule, nativeMarshalerAttributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(), nativeType.ToDisplayString(), type.ToDisplayString())); + return; + } + + bool hasConstructor = false; + bool hasStackallocConstructor = false; + foreach (var ctor in marshalerType.Constructors) + { + if (ctor.IsStatic) + { + continue; + } + if (ctor.Parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(type, ctor.Parameters[0].Type)) + { + hasConstructor = true; + } + if (ctor.Parameters.Length == 2 && SymbolEqualityComparer.Default.Equals(type, ctor.Parameters[0].Type) + && SymbolEqualityComparer.Default.Equals(SpanOfByte, ctor.Parameters[1].Type)) + { + hasStackallocConstructor = true; + } + } + + bool hasToManaged = marshalerType.GetMembers("ToManaged") + .OfType<IMethodSymbol>() + .Any(m => m.Parameters.IsEmpty && !m.ReturnsByRef && !m.ReturnsByRefReadonly && SymbolEqualityComparer.Default.Equals(m.ReturnType, type) && !m.IsStatic); + + // Validate that the native type has at least one marshalling method (either managed to native or native to managed) + if (!hasConstructor && !hasStackallocConstructor && !hasToManaged) + { + context.ReportDiagnostic(Diagnostic.Create(NativeTypeMustHaveRequiredShapeRule, GetSyntaxReferenceForDiagnostic(marshalerType).GetSyntax().GetLocation(), marshalerType.ToDisplayString(), type.ToDisplayString())); + } + + // Validate that this type can support marshalling when stackalloc is not usable. + if (validateAllScenarioSupport && hasStackallocConstructor && !hasConstructor) + { + context.ReportDiagnostic(Diagnostic.Create(StackallocMarshallingShouldSupportAllocatingMarshallingFallbackRule, GetSyntaxReferenceForDiagnostic(marshalerType).GetSyntax().GetLocation(), marshalerType.ToDisplayString())); + } + + IPropertySymbol? valueProperty = nativeType.GetMembers("Value").OfType<IPropertySymbol>().FirstOrDefault(); + if (valueProperty is not null) + { + nativeType = valueProperty.Type; + + // Validate that we don't have partial implementations. + // We error if either of the conditions below are partially met but not fully met: + // - a constructor and a Value property getter + // - a ToManaged method and a Value property setter + if ((hasConstructor || hasStackallocConstructor) && valueProperty.GetMethod is null) + { + context.ReportDiagnostic(Diagnostic.Create(ValuePropertyMustHaveGetterRule, GetSyntaxReferenceForDiagnostic(valueProperty).GetSyntax().GetLocation(), marshalerType.ToDisplayString())); + } + if (hasToManaged && valueProperty.SetMethod is null) + { + context.ReportDiagnostic(Diagnostic.Create(ValuePropertyMustHaveSetterRule, GetSyntaxReferenceForDiagnostic(valueProperty).GetSyntax().GetLocation(), marshalerType.ToDisplayString())); + } + } + + if (!nativeType.IsConsideredBlittable()) + { + context.ReportDiagnostic(Diagnostic.Create(NativeTypeMustBeBlittableRule, + valueProperty is not null + ? GetSyntaxReferenceForDiagnostic(valueProperty).GetSyntax().GetLocation() + : GetSyntaxReferenceForDiagnostic(nativeType).GetSyntax().GetLocation(), + nativeType.ToDisplayString(), + type.ToDisplayString())); + } + + IMethodSymbol? getPinnableReferenceMethod = type.GetMembers("GetPinnableReference") + .OfType<IMethodSymbol>() + .FirstOrDefault(m => m is { Parameters: { Length: 0 } } and ({ ReturnsByRef: true } or { ReturnsByRefReadonly: true })); + if (validateGetPinnableReference && getPinnableReferenceMethod is not null) + { + if (!getPinnableReferenceMethod.ReturnType.IsConsideredBlittable()) + { + context.ReportDiagnostic(Diagnostic.Create(GetPinnableReferenceReturnTypeBlittableRule, getPinnableReferenceMethod.DeclaringSyntaxReferences[0].GetSyntax().GetLocation())); + } + // Validate that the Value property is a pointer-sized primitive type. + if (valueProperty is null || + valueProperty.Type is not ( + IPointerTypeSymbol _ or + { SpecialType: SpecialType.System_IntPtr } or + { SpecialType: SpecialType.System_UIntPtr })) + { + context.ReportDiagnostic(Diagnostic.Create(NativeTypeMustBePointerSizedRule, + valueProperty is not null + ? GetSyntaxReferenceForDiagnostic(valueProperty).GetSyntax().GetLocation() + : GetSyntaxReferenceForDiagnostic(nativeType).GetSyntax().GetLocation(), + nativeType.ToDisplayString(), + type.ToDisplayString())); + } + + // Validate that our marshaler supports scenarios where GetPinnableReference cannot be used. + if (validateAllScenarioSupport && (!hasConstructor || valueProperty is { GetMethod: null })) + { + context.ReportDiagnostic(Diagnostic.Create(GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule, nativeMarshalerAttributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(), type.ToDisplayString())); + } + } + + SyntaxReference GetSyntaxReferenceForDiagnostic(ISymbol targetSymbol) + { + if (targetSymbol.DeclaringSyntaxReferences.IsEmpty) + { + return nativeMarshalerAttributeData.ApplicationSyntaxReference!; + } + else + { + return targetSymbol.DeclaringSyntaxReferences[0]; + } + } + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs new file mode 100644 index 00000000000..6b20376d3b5 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs @@ -0,0 +1,261 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DllImportGenerator { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DllImportGenerator.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to A type marked with 'BlittableTypeAttribute' must be blittable.. + /// </summary> + internal static string BlittableTypeMustBeBlittableDescription { + get { + return ResourceManager.GetString("BlittableTypeMustBeBlittableDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Type '{0}' is marked with 'BlittableTypeAttribute' but is not blittable.. + /// </summary> + internal static string BlittableTypeMustBeBlittableMessage { + get { + return ResourceManager.GetString("BlittableTypeMustBeBlittableMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The 'BlittableTypeAttribute' and 'NativeMarshallingAttributes' are mutually exclusive.. + /// </summary> + internal static string CannotHaveMultipleMarshallingAttributesDescription { + get { + return ResourceManager.GetString("CannotHaveMultipleMarshallingAttributesDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Type '{0}' is marked with 'BlittableTypeAttribute' and 'NativeMarshallingAttribute'. A type can only have one of these two attributes.. + /// </summary> + internal static string CannotHaveMultipleMarshallingAttributesMessage { + get { + return ResourceManager.GetString("CannotHaveMultipleMarshallingAttributesMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable.. + /// </summary> + internal static string GetPinnableReferenceReturnTypeBlittableDescription { + get { + return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The dereferenced type of the return type of the 'GetPinnableReference' method must be blittable.. + /// </summary> + internal static string GetPinnableReferenceReturnTypeBlittableMessage { + get { + return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A type that supports marshalling from managed to native by pinning should also support marshalling from managed to native where pinning is impossible.. + /// </summary> + internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription { + get { + return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible.. + /// </summary> + internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage { + get { + return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A native type for a given type must be blittable.. + /// </summary> + internal static string NativeTypeMustBeBlittableDescription { + get { + return ResourceManager.GetString("NativeTypeMustBeBlittableDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The native type '{0}' for the type '{1}' is not blittable.. + /// </summary> + internal static string NativeTypeMustBeBlittableMessage { + get { + return ResourceManager.GetString("NativeTypeMustBeBlittableMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A native type for a given type must be non-null.. + /// </summary> + internal static string NativeTypeMustBeNonNullDescription { + get { + return ResourceManager.GetString("NativeTypeMustBeNonNullDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The native type for the type '{0}' is null.. + /// </summary> + internal static string NativeTypeMustBeNonNullMessage { + get { + return ResourceManager.GetString("NativeTypeMustBeNonNullMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The native type must be pointer sized so we can cast the pinned result of 'GetPinnableReference' to the native type.. + /// </summary> + internal static string NativeTypeMustBePointerSizedDescription { + get { + return ResourceManager.GetString("NativeTypeMustBePointerSizedDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference" method.. + /// </summary> + internal static string NativeTypeMustBePointerSizedMessage { + get { + return ResourceManager.GetString("NativeTypeMustBePointerSizedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The native type must have at least one of the two marshalling methods to enable marshalling the managed type.. + /// </summary> + internal static string NativeTypeMustHaveRequiredShapeDescription { + get { + return ResourceManager.GetString("NativeTypeMustHaveRequiredShapeDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}'.. + /// </summary> + internal static string NativeTypeMustHaveRequiredShapeMessage { + get { + return ResourceManager.GetString("NativeTypeMustHaveRequiredShapeMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A type that supports marshalling from managed to native by stack allocation should also support marshalling from managed to native where stack allocation is impossible.. + /// </summary> + internal static string StackallocMarshallingShouldSupportAllocatingMarshallingFallbackDescription { + get { + return ResourceManager.GetString("StackallocMarshallingShouldSupportAllocatingMarshallingFallbackDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Native type '{0}' has a stack-allocating constructor does not support marshalling in scenarios where stack allocation is impossible.. + /// </summary> + internal static string StackallocMarshallingShouldSupportAllocatingMarshallingFallbackMessage { + get { + return ResourceManager.GetString("StackallocMarshallingShouldSupportAllocatingMarshallingFallbackMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The native type's 'Value' property must have a getter to support marshalling from managed to native.. + /// </summary> + internal static string ValuePropertyMustHaveGetterDescription { + get { + return ResourceManager.GetString("ValuePropertyMustHaveGetterDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must have a getter.. + /// </summary> + internal static string ValuePropertyMustHaveGetterMessage { + get { + return ResourceManager.GetString("ValuePropertyMustHaveGetterMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The native type's 'Value' property must have a setter to support marshalling from native to managed.. + /// </summary> + internal static string ValuePropertyMustHaveSetterDescription { + get { + return ResourceManager.GetString("ValuePropertyMustHaveSetterDescription", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must have a setter.. + /// </summary> + internal static string ValuePropertyMustHaveSetterMessage { + get { + return ResourceManager.GetString("ValuePropertyMustHaveSetterMessage", resourceCulture); + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx new file mode 100644 index 00000000000..0f0e30a357f --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="BlittableTypeMustBeBlittableDescription" xml:space="preserve"> + <value>A type marked with 'BlittableTypeAttribute' must be blittable.</value> + </data> + <data name="BlittableTypeMustBeBlittableMessage" xml:space="preserve"> + <value>Type '{0}' is marked with 'BlittableTypeAttribute' but is not blittable.</value> + </data> + <data name="CannotHaveMultipleMarshallingAttributesDescription" xml:space="preserve"> + <value>The 'BlittableTypeAttribute' and 'NativeMarshallingAttributes' are mutually exclusive.</value> + </data> + <data name="CannotHaveMultipleMarshallingAttributesMessage" xml:space="preserve"> + <value>Type '{0}' is marked with 'BlittableTypeAttribute' and 'NativeMarshallingAttribute'. A type can only have one of these two attributes.</value> + </data> + <data name="GetPinnableReferenceReturnTypeBlittableDescription" xml:space="preserve"> + <value>The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable.</value> + </data> + <data name="GetPinnableReferenceReturnTypeBlittableMessage" xml:space="preserve"> + <value>The dereferenced type of the return type of the 'GetPinnableReference' method must be blittable.</value> + </data> + <data name="GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription" xml:space="preserve"> + <value>A type that supports marshalling from managed to native by pinning should also support marshalling from managed to native where pinning is impossible.</value> + </data> + <data name="GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage" xml:space="preserve"> + <value>Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible.</value> + </data> + <data name="NativeTypeMustBeBlittableDescription" xml:space="preserve"> + <value>A native type for a given type must be blittable.</value> + </data> + <data name="NativeTypeMustBeBlittableMessage" xml:space="preserve"> + <value>The native type '{0}' for the type '{1}' is not blittable.</value> + </data> + <data name="NativeTypeMustBeNonNullDescription" xml:space="preserve"> + <value>A native type for a given type must be non-null.</value> + </data> + <data name="NativeTypeMustBeNonNullMessage" xml:space="preserve"> + <value>The native type for the type '{0}' is null.</value> + </data> + <data name="NativeTypeMustBePointerSizedDescription" xml:space="preserve"> + <value>The native type must be pointer sized so the pinned result of 'GetPinnableReference' can be cast to the native type.</value> + </data> + <data name="NativeTypeMustBePointerSizedMessage" xml:space="preserve"> + <value>The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference' method.</value> + </data> + <data name="NativeTypeMustHaveRequiredShapeDescription" xml:space="preserve"> + <value>The native type must have at least one of the two marshalling methods to enable marshalling the managed type.</value> + </data> + <data name="NativeTypeMustHaveRequiredShapeMessage" xml:space="preserve"> + <value>The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}'.</value> + </data> + <data name="StackallocMarshallingShouldSupportAllocatingMarshallingFallbackDescription" xml:space="preserve"> + <value>A type that supports marshalling from managed to native by stack allocation should also support marshalling from managed to native where stack allocation is impossible.</value> + </data> + <data name="StackallocMarshallingShouldSupportAllocatingMarshallingFallbackMessage" xml:space="preserve"> + <value>Native type '{0}' has a stack-allocating constructor does not support marshalling in scenarios where stack allocation is impossible.</value> + </data> + <data name="ValuePropertyMustHaveGetterDescription" xml:space="preserve"> + <value>The native type's 'Value' property must have a getter to support marshalling from managed to native.</value> + </data> + <data name="ValuePropertyMustHaveGetterMessage" xml:space="preserve"> + <value>The 'Value' property on the native type '{0}' must have a getter.</value> + </data> + <data name="ValuePropertyMustHaveSetterDescription" xml:space="preserve"> + <value>The native type's 'Value' property must have a setter to support marshalling from native to managed.</value> + </data> + <data name="ValuePropertyMustHaveSetterMessage" xml:space="preserve"> + <value>The 'Value' property on the native type '{0}' must have a setter.</value> + </data> +</root> diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeNames.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeNames.cs new file mode 100644 index 00000000000..2faec2ee43a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeNames.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace DllImportGenerator +{ + static class TypeNames + { + public const string GeneratedMarshallingAttribute = "System.Runtime.InteropServices.GeneratedMarshallingAttribute"; + + public const string BlittableTypeAttribute = "System.Runtime.InteropServices.BlittableTypeAttribute"; + + public const string NativeMarshallingAttribute = "System.Runtime.InteropServices.NativeMarshallingAttribute"; + + public const string MarshalUsingAttribute = "System.Runtime.InteropServices.MarshalUsingAttribute"; + + public const string System_Span = "System.Span`1"; + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeSymbolExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeSymbolExtensions.cs new file mode 100644 index 00000000000..1acefc8c6d7 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeSymbolExtensions.cs @@ -0,0 +1,103 @@ +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Microsoft.Interop +{ + static class TypeSymbolExtensions + { + public static bool HasOnlyBlittableFields(this ITypeSymbol type) + { + if (!type.IsUnmanagedType || !type.IsValueType) + { + return false; + } + + foreach (var field in type.GetMembers().OfType<IFieldSymbol>()) + { + bool? fieldBlittable = field switch + { + { IsStatic : true } => null, + { Type : { IsValueType : false }} => false, + { Type : IPointerTypeSymbol ptr } => IsConsideredBlittable(ptr.PointedAtType), + { Type : IFunctionPointerTypeSymbol } => true, + not { Type : { SpecialType : SpecialType.None }} => IsSpecialTypeBlittable(field.Type.SpecialType), + // A recursive struct type isn't blittable. + // It's also illegal in C#, but I believe that source generators run + // before that is detected, so we check here to avoid a stack overflow. + // [TODO]: Handle mutual recursion. + _ => !SymbolEqualityComparer.Default.Equals(field.Type, type) && IsConsideredBlittable(field.Type) + }; + + if (fieldBlittable is false) + { + return false; + } + } + + return true; + } + + private static bool IsSpecialTypeBlittable(SpecialType specialType) + => specialType switch + { + SpecialType.System_SByte + or SpecialType.System_Byte + or SpecialType.System_Int16 + or SpecialType.System_UInt16 + or SpecialType.System_Int32 + or SpecialType.System_UInt32 + or SpecialType.System_Int64 + or SpecialType.System_UInt64 + or SpecialType.System_Single + or SpecialType.System_Double + or SpecialType.System_IntPtr + or SpecialType.System_UIntPtr => true, + _ => false + }; + + public static bool IsConsideredBlittable(this ITypeSymbol type) + { + if (type.SpecialType != SpecialType.None) + { + return IsSpecialTypeBlittable(type.SpecialType); + } + bool hasNativeMarshallingAttribute = false; + bool hasGeneratedMarshallingAttribute = false; + // [TODO]: Match attributes on full name or symbol, not just on type name. + foreach (var attr in type.GetAttributes()) + { + if (attr.AttributeClass is null) + { + continue; + } + if (attr.AttributeClass.Name == "BlittableTypeAttribute") + { + return true; + } + else if (attr.AttributeClass.Name == "GeneratedMarshallingAttribute") + { + hasGeneratedMarshallingAttribute = true; + } + else if (attr.AttributeClass.Name == "NativeMarshallingAttribute") + { + hasNativeMarshallingAttribute = true; + } + } + + if (hasGeneratedMarshallingAttribute && !hasNativeMarshallingAttribute) + { + // The struct type has generated marshalling via a source generator. + // We can't guarantee that we can see the results of the struct source generator, + // so we re-calculate if the type is blittable here. + return type.HasOnlyBlittableFields(); + } + return false; + } + } +}
\ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/GeneratedMarshallingAttribute.cs b/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/GeneratedMarshallingAttribute.cs new file mode 100644 index 00000000000..a483c22c191 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/GeneratedMarshallingAttribute.cs @@ -0,0 +1,36 @@ +#nullable enable + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + class GeneratedMarshallingAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Struct)] + public class BlittableTypeAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] + public class NativeMarshallingAttribute : Attribute + { + public NativeMarshallingAttribute(Type nativeType) + { + NativeType = nativeType; + } + + public Type NativeType { get; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Field)] + public class MarshalUsingAttribute : Attribute + { + public MarshalUsingAttribute(Type nativeType) + { + NativeType = nativeType; + } + + public Type NativeType { get; } + } +}
\ No newline at end of file |