diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ElementsMarshalling.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ElementsMarshalling.cs index f21125e297aa7e..c547efe7e46d53 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ElementsMarshalling.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ElementsMarshalling.cs @@ -105,6 +105,16 @@ ExpressionSyntax GetExpressionForParam(TypePositionInfo paramInfo) public abstract StatementSyntax GenerateUnmarshalStatement(StubIdentifierContext context); public abstract StatementSyntax GenerateElementCleanupStatement(StubIdentifierContext context); + + /// + /// + /// <numElements> = <GetManagedValuesSource>.Length; + /// + /// + public StatementSyntax GenerateNumElementsAssignmentFromManagedValuesSource(StubIdentifierContext context) + { + return CollectionSource.GetNumElementsAssignmentFromManagedValuesSource(CollectionSource.TypeInfo, context); + } } file static class ElementsMarshallingCollectionSourceExtensions @@ -366,6 +376,7 @@ public override StatementSyntax GenerateManagedToUnmanagedByValueOutUnmarshalSta public override StatementSyntax GenerateElementCleanupStatement(StubIdentifierContext context) { string nativeSpanIdentifier = MarshallerHelpers.GetNativeSpanIdentifier(CollectionSource.TypeInfo, context); + string managedSpanIdentifier = MarshallerHelpers.GetManagedSpanIdentifier(CollectionSource.TypeInfo, context); ExpressionSyntax indexConstraintName; if (!UsesLastIndexMarshalled(CollectionSource.TypeInfo, CollectionSource.CodeContext)) { @@ -394,13 +405,32 @@ public override StatementSyntax GenerateElementCleanupStatement(StubIdentifierCo return EmptyStatement(); } + // Declare the managed span so that nested element cleanup can access it via [] + // (e.g., when an inner stateless collection with NoCountInfo needs to compute its length from the + // managed source during ManagedToUnmanaged cleanup). + bool isManagedToUnmanaged = MarshallerHelpers.GetMarshalDirection(CollectionSource.TypeInfo, CollectionSource.CodeContext) == MarshalDirection.ManagedToUnmanaged; + LocalDeclarationStatementSyntax nativeSpanDeclaration = Declare( + ReadOnlySpanOf(unmanagedElementType), + nativeSpanIdentifier, + isManagedToUnmanaged + ? CollectionSource.GetUnmanagedValuesDestination(context) + : CollectionSource.GetUnmanagedValuesSource(context)); + + if (isManagedToUnmanaged) + { + LocalDeclarationStatementSyntax managedSpanDeclaration = Declare( + ReadOnlySpanOf(elementMarshaller.TypeInfo.ManagedType.Syntax), + managedSpanIdentifier, + CollectionSource.GetManagedValuesSource(context)); + + return Block( + nativeSpanDeclaration, + managedSpanDeclaration, + contentsCleanupStatements); + } + return Block( - Declare( - ReadOnlySpanOf(unmanagedElementType), - nativeSpanIdentifier, - MarshallerHelpers.GetMarshalDirection(CollectionSource.TypeInfo, CollectionSource.CodeContext) == MarshalDirection.ManagedToUnmanaged - ? CollectionSource.GetUnmanagedValuesDestination(context) - : CollectionSource.GetUnmanagedValuesSource(context)), + nativeSpanDeclaration, contentsCleanupStatements); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatelessMarshallingStrategy.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatelessMarshallingStrategy.cs index da13f490614238..31363037d8f2b1 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatelessMarshallingStrategy.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StatelessMarshallingStrategy.cs @@ -581,15 +581,9 @@ public IEnumerable GenerateCleanupCallerAllocatedResourcesState if (countInfo is NoCountInfo && MarshallerHelpers.GetMarshalDirection(TypeInfo, CodeContext) == MarshalDirection.ManagedToUnmanaged) { // When marshalling from managed to unmanaged, we may not have count info. - // For now, just set to 0. - // See https://github.com/dotnet/runtime/issues/93423 for a tracking issue. - - // = 0; - yield return ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(numElementsIdentifier), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)))); + // Use the managed collection's length to determine the number of elements to clean up. + // = .Length; + yield return elementsMarshalling.GenerateNumElementsAssignmentFromManagedValuesSource(context); } else { diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs index 7b853db968adb1..c71eb7b5ede179 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs @@ -314,11 +314,13 @@ public static IEnumerable CustomCollectionsManagedToUnmanaged(Generato yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateless.NativeToManagedOnlyOutParameter() }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateless.NativeToManagedOnlyReturnValue() }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateless.NonBlittableElementByValue }; + yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateless.NonBlittableElementWithFreeByValue }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateless.NonBlittableElementNativeToManagedOnlyOutParameter }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateless.NonBlittableElementNativeToManagedOnlyReturnValue }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateful.NativeToManagedOnlyOutParameter() }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateful.NativeToManagedOnlyReturnValue() }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateful.NonBlittableElementByValue }; + yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateful.NonBlittableElementWithFreeByValue }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateful.NonBlittableElementNativeToManagedOnlyOutParameter }; yield return new[] { ID(), customCollectionMarshallingCodeSnippetsManagedToUnmanaged.Stateful.NonBlittableElementNativeToManagedOnlyReturnValue }; } diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/CustomCollectionMarshallingCodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/CustomCollectionMarshallingCodeSnippets.cs index f25a50341e9d00..27a83c0aa5a15b 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/Common/CustomCollectionMarshallingCodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/CustomCollectionMarshallingCodeSnippets.cs @@ -54,6 +54,16 @@ public struct Native { } public static Element ConvertToManaged(Native n) => throw null; } """; + public const string ElementInWithFree = """ + [CustomMarshaller(typeof(Element), MarshalMode.ElementIn, typeof(ElementMarshaller))] + static class ElementMarshaller + { + public struct Native { } + public static Native ConvertToUnmanaged(Element e) => throw null; + public static Element ConvertToManaged(Native n) => throw null; + public static void Free(Native unmanaged) { } + } + """; public const string ElementOut = """ [CustomMarshaller(typeof(Element), MarshalMode.ElementOut, typeof(ElementMarshaller))] static class ElementMarshaller @@ -252,6 +262,10 @@ public string NestedMarshallerParametersAndModifiers(string elementType) => _pro + NonBlittableElement + ElementIn; + public string NonBlittableElementWithFreeByValue => ByValue("Element") + + NonBlittableElement + + ElementInWithFree; + public string NonBlittableElementNativeToManagedOnlyOutParameter => NativeToManagedOnlyOutParameter("Element") + NonBlittableElement + ElementOut; @@ -508,6 +522,10 @@ public string NativeToManagedFinallyOnlyReturnValue(string elementType) => _snip + NonBlittableElement + ElementIn; + public string NonBlittableElementWithFreeByValue => ByValue("Element") + + NonBlittableElement + + ElementInWithFree; + public string NonBlittableElementNativeToManagedOnlyOutParameter => NativeToManagedOnlyOutParameter("Element") + NonBlittableElement + ElementOut; diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs index dd2759297b3a20..09dbefdea4f783 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs @@ -177,13 +177,33 @@ public void MultidimensionalArray_CheckOuterArrayIsIndexTracked() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/93423")] public void MultidimensionalArray_CheckInnerArraysAreCleared() { var arr = GetMultiDimensionalArray(10, 10); foreach (var throwOn in new int[] { 0, 1, 45, 99 }) { BoolStructInMarshallerAllowNull.Marshaller.MarshallingFailsIndex = throwOn; + // https://github.com/dotnet/runtime/issues/93431 + // We currently only clean up inner arrays that were fully marshalled + // (i.e. all elements in the outer collection up to the last fully completed inner array). + BoolStructInMarshallerAllowNull.Marshaller.ExpectedFreeCount = throwOn - throwOn % 10; + Assert.Throws(() => + { + NativeExportsNE.MarshallingFails.MarshalMultidimensionalArray_CheckInnerArraysAreCleared(arr); + }); + BoolStructInMarshallerAllowNull.Marshaller.AssertAllHaveBeenCleaned(); + } + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/93431")] + public void MultidimensionalArray_CheckInnerArraysAreCleared_ProperCleanup() + { + var arr = GetMultiDimensionalArray(10, 10); + foreach (var throwOn in new int[] { 0, 1, 45, 99 }) + { + BoolStructInMarshallerAllowNull.Marshaller.MarshallingFailsIndex = throwOn; + // Expected Behavior - Should free all elements of inner arrays that were partially marshalled BoolStructInMarshallerAllowNull.Marshaller.ExpectedFreeCount = throwOn; Assert.Throws(() => { @@ -230,27 +250,20 @@ public void MultiDimensionalOutArray_EnsureAllCleaned() // Set up unmarshalling asserts BoolStructOutMarshaller.Marshaller.UnmarshallingFailsIndex = throwOn; BoolStructOutMarshaller.Marshaller.ExpectedFreedValues = Enumerable.Range(0, 100).Select(_ => new BoolStructNative() { b1 = 1, b2 = 1, b3 = 1 }).ToArray(); - // https://github.com/dotnet/runtime/issues/93423 - //NegateBoolStructInMarshaller.Marshaller.ExpectedFreedValues = Enumerable.Range(0, 100).Select(_ => new BoolStructNative() { b1 = 0, b2 = 0, b3 = 0 }).ToArray(); + BoolStructInMarshaller.Marshaller.ExpectedFreedValues = Enumerable.Range(0, 100).Select(_ => new BoolStructNative() { b1 = 0, b2 = 0, b3 = 0 }).ToArray(); Assert.Throws(() => { NativeExportsNE.MarshallingFails.NegateBoolsOut2D(arr, arr.Length, widths, out BoolStruct[][] boolsOut); }); - // https://github.com/dotnet/runtime/issues/93423 - //NegateBoolStructInMarshaller.Marshaller.AssertAllHaveBeenCleaned(); - BoolStructInMarshaller.Marshaller.Reset(); + BoolStructInMarshaller.Marshaller.AssertAllHaveBeenCleaned(); BoolStructOutMarshaller.Marshaller.AssertAllHaveBeenCleaned(); } // Run without throwing - this is okay only because the native code doesn't actually use the array, it creates a whole new one BoolStructOutMarshaller.Marshaller.UnmarshallingFailsIndex = -1; BoolStructOutMarshaller.Marshaller.ExpectedFreedValues = Enumerable.Range(0, 100).Select(_ => new BoolStructNative() { b1 = 1, b2 = 1, b3 = 1 }).ToArray(); - // https://github.com/dotnet/runtime/issues/93423 - //NegateBoolStructInMarshaller.Marshaller.UnmarshallingFailsIndex = -1; - //NegateBoolStructInMarshaller.Marshaller.ExpectedFreeCount = 100; + BoolStructInMarshaller.Marshaller.ExpectedFreedValues = Enumerable.Range(0, 100).Select(_ => new BoolStructNative() { b1 = 0, b2 = 0, b3 = 0 }).ToArray(); NativeExportsNE.MarshallingFails.NegateBoolsOut2D(arr, arr.Length, widths, out BoolStruct[][] boolsOut); - // https://github.com/dotnet/runtime/issues/93423 - //NegateBoolStructInMarshaller.Marshaller.AssertAllHaveBeenCleaned(); - BoolStructInMarshaller.Marshaller.Reset(); + BoolStructInMarshaller.Marshaller.AssertAllHaveBeenCleaned(); BoolStructOutMarshaller.Marshaller.AssertAllHaveBeenCleaned(); } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index 81b10efc82d4af..d11f2942f4ec9f 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -394,6 +394,7 @@ public static IEnumerable CustomCollections() yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateless.NativeToManagedFinallyOnlyReturnValue() }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateless.NestedMarshallerParametersAndModifiers() }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateless.NonBlittableElementByValue }; + yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateless.NonBlittableElementWithFreeByValue }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateless.NonBlittableElementParametersAndModifiers }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateless.NonBlittableElementNativeToManagedOnlyOutParameter }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateless.NonBlittableElementNativeToManagedFinallyOnlyOutParameter }; @@ -432,6 +433,7 @@ public static IEnumerable CustomCollections() yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateful.NativeToManagedOnlyReturnValue() }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateful.NativeToManagedFinallyOnlyReturnValue() }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateful.NonBlittableElementByValue }; + yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateful.NonBlittableElementWithFreeByValue }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateful.NonBlittableElementParametersAndModifiers }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateful.NonBlittableElementNativeToManagedOnlyOutParameter }; yield return new[] { ID(), customCollectionMarshallingCodeSnippets.Stateful.NonBlittableElementNativeToManagedFinallyOnlyOutParameter };