From 1e3f71c9487373da9b83e248c82c7e4dbc6c77b1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 3 May 2021 20:23:37 -0700 Subject: [PATCH] Add support for null arrays in the blittable array marshaller's pinning optimization. (dotnet/runtimelab#1063) * Add support for null arrays in the blittable array marshaller's pinning optimization. * Add comment block about the behavior. * Use pointer casting instead of Unsafe.NullRef since we are already using pointers. Commit migrated from https://github.com/dotnet/runtimelab/commit/c436fda400ce8b6bc374a69f53d02091d8e968f3 --- .../Marshalling/BlittableArrayMarshaller.cs | 53 ++++++++++++++++--- .../gen/DllImportGenerator/TypeNames.cs | 2 +- .../DllImportGenerator.Tests/ArrayTests.cs | 8 ++- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs index 8e7291f9158b3b..cd42a96d832459 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs @@ -56,21 +56,58 @@ public override IEnumerable Generate(TypePositionInfo info, Stu var (managedIdentifer, nativeIdentifier) = context.GetIdentifiers(info); if (!info.IsByRef && !info.IsManagedReturnPosition && context.PinningSupported) { + string byRefIdentifier = $"__byref_{managedIdentifer}"; + if (context.CurrentStage == StubCodeContext.Stage.Marshal) + { + // [COMPAT] We use explicit byref calculations here instead of just using a fixed statement + // since a fixed statement converts a zero-length array to a null pointer. + // Many native APIs, such as GDI+, ICU, etc. validate that an array parameter is non-null + // even when the passed in array length is zero. To avoid breaking customers that want to move + // to source-generated interop in subtle ways, we explicitly pass a reference to the 0-th element + // of an array as long as it is non-null, matching the behavior of the built-in interop system + // for single-dimensional zero-based arrays. + + // ref = == null ? ref *(); + var nullRef = + PrefixUnaryExpression(SyntaxKind.PointerIndirectionExpression, + CastExpression( + PointerType(GetElementTypeSyntax(info)), + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)))); + + var getArrayDataReference = + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParseTypeName(TypeNames.System_Runtime_InteropServices_MemoryMarshal), + IdentifierName("GetArrayDataReference")), + ArgumentList(SingletonSeparatedList( + Argument(IdentifierName(managedIdentifer))))); + + yield return LocalDeclarationStatement( + VariableDeclaration( + RefType(GetElementTypeSyntax(info))) + .WithVariables(SingletonSeparatedList( + VariableDeclarator(Identifier(byRefIdentifier)) + .WithInitializer(EqualsValueClause( + RefExpression(ParenthesizedExpression( + ConditionalExpression( + BinaryExpression( + SyntaxKind.EqualsExpression, + IdentifierName(managedIdentifer), + LiteralExpression( + SyntaxKind.NullLiteralExpression)), + RefExpression(nullRef), + RefExpression(getArrayDataReference))))))))); + } if (context.CurrentStage == StubCodeContext.Stage.Pin) { - // fixed ( = &MemoryMarshal.GetArrayDataReference()) + // fixed ( = &) yield return FixedStatement( VariableDeclaration(AsNativeType(info), SingletonSeparatedList( VariableDeclarator(nativeIdentifier) .WithInitializer(EqualsValueClause( PrefixUnaryExpression(SyntaxKind.AddressOfExpression, - InvocationExpression( - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - ParseTypeName(TypeNames.System_Runtime_InteropServices_MemoryMarshal), - IdentifierName("GetArrayDataReference")), - ArgumentList( - SingletonSeparatedList(Argument(IdentifierName(managedIdentifer))) - ))))))), + IdentifierName(byRefIdentifier)))))), EmptyStatement()); } yield break; diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeNames.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeNames.cs index 457a4db0386a54..509969a0523060 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeNames.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/TypeNames.cs @@ -44,7 +44,7 @@ public static string MarshalEx(AnalyzerConfigOptions options) public const string System_Runtime_InteropServices_OutAttribute = "System.Runtime.InteropServices.OutAttribute"; public const string System_Runtime_InteropServices_InAttribute = "System.Runtime.InteropServices.InAttribute"; - + public const string System_Runtime_CompilerServices_SkipLocalsInitAttribute = "System.Runtime.CompilerServices.SkipLocalsInitAttribute"; } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.Tests/ArrayTests.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.Tests/ArrayTests.cs index d535c13d7691cf..0a5b67328152c6 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.Tests/ArrayTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.Tests/ArrayTests.cs @@ -67,10 +67,16 @@ public class ArrayTests [Fact] public void IntArrayMarshalledToNativeAsExpected() { - var array = new [] { 1, 5, 79, 165, 32, 3 }; + var array = new[] { 1, 5, 79, 165, 32, 3 }; Assert.Equal(array.Sum(), NativeExportsNE.Arrays.Sum(array, array.Length)); } + [Fact] + public void NullIntArrayMarshalledToNativeAsExpected() + { + Assert.Equal(-1, NativeExportsNE.Arrays.Sum(null, 0)); + } + [Fact] public void ZeroLengthArrayMarshalledAsNonNull() {