diff --git a/test/Microsoft.Windows.CsWin32.Tests/InteriorPointerTests.cs b/test/Microsoft.Windows.CsWin32.Tests/InteriorPointerTests.cs index 4b82dcdd..d47a8152 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/InteriorPointerTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/InteriorPointerTests.cs @@ -2,127 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. /// -/// Tests projections of native buffers that contain borrowed interior pointers. +/// Tests projections of native buffers using the published Win32 metadata package. /// public class InteriorPointerTests : GeneratorTestBase { - private const string MetadataSource = """ - using System; - using System.Runtime.InteropServices; - using Windows.Win32.Foundation.Metadata; - - namespace Windows.Win32.Foundation.Metadata - { - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class ContainsInteriorPointersAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class MemorySizeAttribute : Attribute - { - public short BytesParamIndex { get; set; } - } - - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class NativeArrayInfoAttribute : Attribute - { - public short CountParamIndex { get; set; } - public int CountConst { get; set; } - } - - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class ConstAttribute : Attribute { } - } - - namespace Windows.Win32.Test - { - public unsafe struct RECORD - { - public byte* Value; - } - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void CALLBACK(); - - public static unsafe class Apis - { - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void ByteBuffer( - [ContainsInteriorPointers] [Out] byte* buffer, uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void MemorySizedByteBuffer( - [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] byte* buffer, - uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void VoidBuffer( - [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] void* buffer, - uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void RecordBuffer( - [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] RECORD* buffer, - uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void CountedRecordBuffer( - [ContainsInteriorPointers] [Out, NativeArrayInfo(CountParamIndex = 1)] RECORD* buffer, - uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void ConstantRecordBuffer( - [ContainsInteriorPointers] [Out, NativeArrayInfo(CountConst = 1)] RECORD* buffer, - [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void OptionalRecordBuffer( - [ContainsInteriorPointers] [Out, Optional, MemorySize(BytesParamIndex = 1)] RECORD* buffer, - uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void OptionalCapacityBuffer( - [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] byte* buffer, - [In, Out, Optional] uint* capacity, [Out, Optional] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void ConstRecordBuffer( - [ContainsInteriorPointers] [In, Const, MemorySize(BytesParamIndex = 1)] RECORD* buffer, - uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void SingleRecord( - [ContainsInteriorPointers] [Out] RECORD* buffer, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void ScalarBuffer( - [ContainsInteriorPointers] [Out] uint* buffer, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void CallbackBuffer( - [ContainsInteriorPointers] [Out, NativeArrayInfo(CountParamIndex = 1)] CALLBACK* buffer, - uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void MixedBuffers( - [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] void* buffer, - uint capacity, [Out, MemorySize(BytesParamIndex = 3)] byte* ordinaryBuffer, - uint ordinaryCapacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void SharedCapacity( - [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 2)] byte* buffer, - [Out, MemorySize(BytesParamIndex = 2)] byte* ordinaryBuffer, - uint capacity, [Out] uint* written); - - [DllImport("kernel32.dll", ExactSpelling = true)] - public static extern void SharedCapacityReversed( - [Out, NativeArrayInfo(CountParamIndex = 2)] RECORD* ordinaryBuffer, - [ContainsInteriorPointers] [Out, NativeArrayInfo(CountParamIndex = 2)] RECORD* buffer, - uint capacity, [Out] uint* written); - } - } - """; - /// /// Initializes a new instance of the class. /// @@ -228,154 +111,40 @@ public void PublishedMetadataBuffersRemainPointers(string api, string bufferName } /// - /// Verifies that the published annotation does not suppress ordinary buffer projections. + /// Verifies that annotated real APIs retain friendly handle and scalar output projections. /// - /// Whether to enable marshaling. - [Theory, CombinatorialData] - public void PublishedMetadataUnannotatedBuffersStillUseSpans(bool allowMarshaling) + /// The annotated API to generate. + /// The handle parameter. + /// The scalar output parameter. + [Theory] + [InlineData("GetPrinterW", "hPrinter", "pcbNeeded")] + [InlineData("GetTokenInformation", "TokenHandle", "ReturnLength")] + public void PublishedMetadataOtherParametersRemainFriendly(string api, string handleName, string outputName) { this.compilation = this.starterCompilations["net8.0"]; - this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { AllowMarshaling = allowMarshaling }); - this.GenerateApi("ReadFile"); - - Assert.Contains(this.FindGeneratedMethod("ReadFile"), method => - method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "lpBuffer" - && parameter.Type is GenericNameSyntax { Identifier.ValueText: "Span" })); - } - - /// - /// Verifies that annotated buffers retain native pointer types and explicit capacities in every overload. - /// - /// The buffer shape to generate. - /// Whether to enable marshaling. - /// Whether additional pointer overloads are requested. - [Theory, CombinatorialData] - public void AnnotatedBuffersRemainPointers( - [CombinatorialValues("ByteBuffer", "MemorySizedByteBuffer", "VoidBuffer", "RecordBuffer", "CountedRecordBuffer", "ConstantRecordBuffer", "OptionalRecordBuffer", "OptionalCapacityBuffer", "ConstRecordBuffer", "SingleRecord", "ScalarBuffer", "CallbackBuffer")] string api, - bool allowMarshaling, - bool includePointerOverloads) - { - this.GenerateFromMetadata(api, containsInteriorPointers: true, allowMarshaling, includePointerOverloads); - - MethodDeclarationSyntax[] methods = this.FindGeneratedMethod(api).ToArray(); - Assert.NotEmpty(methods); - Assert.All(methods, method => - { - ParameterSyntax buffer = Assert.Single(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "buffer"); - Assert.IsType(buffer.Type); - Assert.Empty(buffer.Modifiers); - Assert.Equal(api is not ("ConstantRecordBuffer" or "SingleRecord" or "ScalarBuffer"), method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "capacity")); - }); - - Assert.Contains(methods, method => !IsOrContainsExternMethod(method) - && method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "written" && parameter.Modifiers.Any(SyntaxKind.OutKeyword))); - } - - /// - /// Verifies that ordinary buffer projections remain available when the annotation is absent. - /// - /// The buffer shape to generate. - /// Whether to enable marshaling. - [Theory, CombinatorialData] - public void UnannotatedBuffersStillUseSpans( - [CombinatorialValues("ByteBuffer", "MemorySizedByteBuffer", "VoidBuffer", "RecordBuffer", "CountedRecordBuffer", "ConstantRecordBuffer", "OptionalRecordBuffer", "OptionalCapacityBuffer", "ConstRecordBuffer")] string api, - bool allowMarshaling) - { - this.GenerateFromMetadata(api, containsInteriorPointers: false, allowMarshaling, includePointerOverloads: false); - Assert.Contains(this.FindGeneratedMethod(api), method => method.ParameterList.Parameters.Any(parameter => - parameter.Identifier.ValueText == "buffer" - && parameter.Type is GenericNameSyntax { Identifier.ValueText: "Span" or "ReadOnlySpan" })); - } - - /// - /// Verifies that unrelated buffers and scalar outputs still receive friendly projections. - /// - /// Whether to enable marshaling. - /// Whether additional pointer overloads are requested. - [Theory, CombinatorialData] - public void OtherParametersRetainFriendlyProjections(bool allowMarshaling, bool includePointerOverloads) - { - const string Api = "MixedBuffers"; - this.GenerateFromMetadata(Api, containsInteriorPointers: true, allowMarshaling, includePointerOverloads); - - Assert.All(this.FindGeneratedMethod(Api), method => - { - Assert.IsType(Assert.Single(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "buffer").Type); - Assert.Contains(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "capacity"); - }); - Assert.Contains(this.FindGeneratedMethod(Api), method => - method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "ordinaryBuffer" && parameter.Type is GenericNameSyntax { Identifier.ValueText: "Span" }) - && !method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "ordinaryCapacity")); - } + this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { WideCharOnly = false }); + this.GenerateApi(api); - /// - /// Verifies that other buffers cannot remove a capacity needed by an annotated pointer buffer. - /// - /// The order and size annotation to generate. - /// Whether to enable marshaling. - [Theory, CombinatorialData] - public void SharedCapacitiesRemainExplicit( - [CombinatorialValues("SharedCapacity", "SharedCapacityReversed")] string api, - bool allowMarshaling) - { - this.GenerateFromMetadata(api, containsInteriorPointers: true, allowMarshaling, includePointerOverloads: false); - Assert.All(this.FindGeneratedMethod(api), method => - { - Assert.Contains(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "capacity"); - Assert.All(method.ParameterList.Parameters.Where(parameter => parameter.Identifier.ValueText is "buffer" or "ordinaryBuffer"), parameter => Assert.IsType(parameter.Type)); - }); + Assert.Contains(this.FindGeneratedMethod(api), method => + method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == handleName + && parameter.Type is IdentifierNameSyntax { Identifier.ValueText: "SafeHandle" }) + && method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == outputName + && parameter.Modifiers.Any(SyntaxKind.OutKeyword))); } /// - /// Verifies that optional capacities remain explicit while unrelated optional outputs can still be omitted. + /// Verifies that the published annotation does not suppress ordinary buffer projections. /// /// Whether to enable marshaling. [Theory, CombinatorialData] - public void OptionalCapacitiesRemainExplicit(bool allowMarshaling) - { - const string Api = "OptionalCapacityBuffer"; - this.GenerateFromMetadata(Api, containsInteriorPointers: true, allowMarshaling, includePointerOverloads: false); - - MethodDeclarationSyntax[] methods = this.FindGeneratedMethod(Api).ToArray(); - Assert.All(methods, method => - Assert.Contains(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "capacity")); - Assert.Contains(methods, method => - method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "capacity" && parameter.Modifiers.Any(SyntaxKind.RefKeyword))); - Assert.Contains(methods, method => - !method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "written")); - } - - private void GenerateFromMetadata(string api, bool containsInteriorPointers, bool allowMarshaling, bool includePointerOverloads) + public void PublishedMetadataUnannotatedBuffersStillUseSpans(bool allowMarshaling) { this.compilation = this.starterCompilations["net8.0"]; - string source = containsInteriorPointers ? MetadataSource : MetadataSource.Replace("[ContainsInteriorPointers]", string.Empty, StringComparison.Ordinal); - CSharpCompilation metadataCompilation = CSharpCompilation.Create( - "Windows.Win32", - [CSharpSyntaxTree.ParseText(source, this.parseOptions)], - this.compilation.References, - this.compilation.Options); - string metadataPath = Path.Combine(Path.GetTempPath(), $"CsWin32-InteriorPointers-{Guid.NewGuid():N}.winmd"); - try - { - using (FileStream stream = File.Create(metadataPath)) - { - var result = metadataCompilation.Emit(stream); - Assert.True(result.Success, string.Join(Environment.NewLine, result.Diagnostics)); - } + this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { AllowMarshaling = allowMarshaling }); + this.GenerateApi("ReadFile"); - GeneratorOptions options = DefaultTestGeneratorOptions with - { - AllowMarshaling = allowMarshaling, - FriendlyOverloads = new() { IncludePointerOverloads = includePointerOverloads }, - }; - this.generator = new Generator(metadataPath, null, [], options, this.compilation, this.parseOptions); - this.GenerateApi(api); - } - finally - { - this.generator?.Dispose(); - this.generator = null; - File.Delete(metadataPath); - } + Assert.Contains(this.FindGeneratedMethod("ReadFile"), method => + method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "lpBuffer" + && parameter.Type is GenericNameSyntax { Identifier.ValueText: "Span" })); } }