This commit is contained in:
Jackson Schuster 2025-07-30 13:25:28 +02:00 committed by GitHub
commit e1fe2f5c96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 945 additions and 13 deletions

View File

@ -58,7 +58,12 @@ namespace System.Runtime.InteropServices.Marshalling
/// <param name="numElements">The unmanaged element count.</param>
/// <returns>The <see cref="Span{TUnmanagedElement}"/> of unmanaged elements.</returns>
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
=> new Span<TUnmanagedElement>(unmanaged, numElements);
{
if (unmanaged is null)
return [];
return new Span<TUnmanagedElement>(unmanaged, numElements);
}
/// <summary>
/// Allocates memory for the managed representation of the array.
@ -89,7 +94,12 @@ namespace System.Runtime.InteropServices.Marshalling
/// <param name="numElements">The unmanaged element count.</param>
/// <returns>The <see cref="ReadOnlySpan{TUnmanagedElement}"/> containing the unmanaged elements to marshal.</returns>
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanagedValue, int numElements)
=> new ReadOnlySpan<TUnmanagedElement>(unmanagedValue, numElements);
{
if (unmanagedValue is null)
return [];
return new ReadOnlySpan<TUnmanagedElement>(unmanagedValue, numElements);
}
/// <summary>
/// Frees memory for the unmanaged array.

View File

@ -59,7 +59,11 @@ namespace System.Runtime.InteropServices.Marshalling
/// <param name="numElements">The unmanaged element count.</param>
/// <returns>The <see cref="Span{TUnmanagedElement}"/> of unmanaged elements.</returns>
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
=> new Span<TUnmanagedElement>(unmanaged, numElements);
{
if (unmanaged is null)
return [];
return new Span<TUnmanagedElement>(unmanaged, numElements);
}
/// <summary>
/// Allocates memory for the managed representation of the array.
@ -90,7 +94,12 @@ namespace System.Runtime.InteropServices.Marshalling
/// <param name="numElements">The unmanaged element count.</param>
/// <returns>The <see cref="ReadOnlySpan{TUnmanagedElement}"/> containing the unmanaged elements to marshal.</returns>
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanagedValue, int numElements)
=> new ReadOnlySpan<TUnmanagedElement>(unmanagedValue, numElements);
{
if (unmanagedValue is null)
return [];
return new ReadOnlySpan<TUnmanagedElement>(unmanagedValue, numElements);
}
/// <summary>
/// Frees memory for the unmanaged array.

View File

@ -69,7 +69,12 @@ namespace System.Runtime.InteropServices.Marshalling
/// <param name="numElements">The number of elements that will be copied into the memory block.</param>
/// <returns>A span over the unmanaged memory that can contain the specified number of elements.</returns>
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
=> new Span<TUnmanagedElement>(unmanaged, numElements);
{
if (unmanaged == null)
return [];
return new Span<TUnmanagedElement>(unmanaged, numElements);
}
}
/// <summary>
@ -201,6 +206,8 @@ namespace System.Runtime.InteropServices.Marshalling
/// <returns>A span over unmanaged values of the array.</returns>
public ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(int numElements)
{
if (_unmanagedArray is null)
return [];
return new ReadOnlySpan<TUnmanagedElement>(_unmanagedArray, numElements);
}

View File

@ -63,7 +63,11 @@ namespace System.Runtime.InteropServices.Marshalling
/// <param name="numElements">The number of elements that will be copied into the memory block.</param>
/// <returns>A span over the unmanaged memory that can contain the specified number of elements.</returns>
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
=> new Span<TUnmanagedElement>(unmanaged, numElements);
{
if (unmanaged == null)
return [];
return new Span<TUnmanagedElement>(unmanaged, numElements);
}
/// <summary>
/// Allocates space to store the managed elements.
@ -94,7 +98,11 @@ namespace System.Runtime.InteropServices.Marshalling
/// <param name="numElements">The number of elements in the unmanaged collection.</param>
/// <returns>A span over the native collection elements.</returns>
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
=> new ReadOnlySpan<TUnmanagedElement>(unmanaged, numElements);
{
if (unmanaged == null)
return [];
return new ReadOnlySpan<TUnmanagedElement>(unmanaged, numElements);
}
/// <summary>
/// Frees the allocated unmanaged memory.

View File

@ -39,7 +39,6 @@ namespace Microsoft.Interop
/// </code>
/// </summary>
public StatementSyntax GenerateClearUnmanagedDestination(StubIdentifierContext context)
{
// <GetUnmanagedValuesDestination>.Clear();
return MethodInvocationStatement(
@ -284,7 +283,7 @@ namespace Microsoft.Interop
statements.Add(GenerateContentsMarshallingStatement(
context,
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(MarshallerHelpers.GetManagedSpanIdentifier(CollectionSource.TypeInfo, context)),
IdentifierName(managedSpanIdentifier),
IdentifierName("Length")),
elementMarshaller,
StubIdentifierContext.Stage.Marshal));
@ -295,7 +294,6 @@ namespace Microsoft.Interop
{
string managedSpanIdentifier = MarshallerHelpers.GetManagedSpanIdentifier(CollectionSource.TypeInfo, context);
string nativeSpanIdentifier = MarshallerHelpers.GetNativeSpanIdentifier(CollectionSource.TypeInfo, context);
string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(CollectionSource.TypeInfo, context);
// ReadOnlySpan<TUnmanagedElement> <nativeSpan> = <GetUnmanagedValuesSource>
// Span<T> <managedSpan> = <GetManagedValuesDestination>
@ -311,7 +309,9 @@ namespace Microsoft.Interop
CollectionSource.GetManagedValuesDestination(context)),
GenerateContentsMarshallingStatement(
context,
IdentifierName(numElementsIdentifier),
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nativeSpanIdentifier),
IdentifierName("Length")),
elementMarshaller,
StubIdentifierContext.Stage.UnmarshalCapture, StubIdentifierContext.Stage.Unmarshal));
}
@ -356,7 +356,9 @@ namespace Microsoft.Interop
unmanagedValuesDeclaration,
GenerateContentsMarshallingStatement(
context,
IdentifierName(numElementsIdentifier),
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(managedSpanIdentifier),
IdentifierName("Length")),
elementMarshaller,
StubIdentifierContext.Stage.UnmarshalCapture, StubIdentifierContext.Stage.Unmarshal));
}
@ -460,7 +462,9 @@ namespace Microsoft.Interop
managedValuesDestination,
GenerateContentsMarshallingStatement(
context,
IdentifierName(numElementsIdentifier),
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nativeSpanIdentifier),
IdentifierName("Length")),
new FreeAlwaysOwnedOriginalValueGenerator(elementMarshaller),
stagesToGenerate));
}

View File

@ -0,0 +1,244 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using SharedTypes.ComInterfaces;
using SharedTypes;
using Xunit;
namespace ComInterfaceGenerator.Tests
{
/// <summary>
/// Tests for edge cases involving null arrays when their length parameters are non-zero.
/// This addresses https://github.com/dotnet/runtime/issues/118135
/// </summary>
public unsafe partial class INullArrayTests
{
private static INullArrayCases CreateTestInterface()
{
INullArrayCases originalObject = new INullArrayCasesImpl();
ComWrappers cw = new StrategyBasedComWrappers();
nint ptr = cw.GetOrCreateComInterfaceForObject(originalObject, CreateComInterfaceFlags.None);
object obj = cw.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.None);
return (INullArrayCases)obj;
}
[Fact]
public void SingleNullArray_WithNonZeroLength_DoesNotCrash()
{
// Arrange
var testInterface = CreateTestInterface();
// Act & Assert - Should not throw or crash
testInterface.SingleNullArrayWithLength(10, null);
}
[Fact]
public void SingleNullArray_WithZeroLength_DoesNotCrash()
{
var testInterface = CreateTestInterface();
// Should not throw or crash
testInterface.SingleNullArrayWithLength(0, null);
}
[Fact]
public void SingleNullArray_WithValidArray_WorksNormally()
{
var testInterface = CreateTestInterface();
int[] array = new int[5];
testInterface.SingleNullArrayWithLength(5, array);
Assert.Equal(new int[] { 0, 2, 4, 6, 8 }, array);
}
[Fact]
public void MultipleArrays_SomeNull_DoesNotCrash()
{
var testInterface = CreateTestInterface();
int[] array1 = new int[3];
int[] array3 = new int[3];
testInterface.MultipleArraysSharedLength(3, array1, null, array3);
Assert.Equal(new int[] { 0, 1, 2 }, array1);
Assert.Equal(new int[] { 0, 100, 200 }, array3);
}
[Fact]
public void MultipleArrays_AllNull_DoesNotCrash()
{
var testInterface = CreateTestInterface();
testInterface.MultipleArraysSharedLength(5, null, null, null);
}
[Fact]
public void NonBlittableArray_Null_DoesNotCrash()
{
var testInterface = CreateTestInterface();
testInterface.NonBlittableNullArray(10, null);
}
[Fact]
public void NonBlittableArray_ValidArray_WorksNormally()
{
var testInterface = CreateTestInterface();
var array = new IntStructWrapper[3];
testInterface.NonBlittableNullArray(3, array);
Assert.Equal(0, array[0].Value);
Assert.Equal(3, array[1].Value);
Assert.Equal(6, array[2].Value);
}
[Fact]
public void ZeroLength_NullArray_DoesNotCrash()
{
var testInterface = CreateTestInterface();
testInterface.ZeroLengthArray(0, null);
}
[Fact]
public void LargeLength_NullArray_DoesNotCrash()
{
var testInterface = CreateTestInterface();
testInterface.LargeLengthNullArray(int.MaxValue, null);
}
[Fact]
public void SpanNull_DoesNotCrash()
{
var testInterface = CreateTestInterface();
var (__this, __vtable) = ((IUnmanagedVirtualMethodTableProvider)testInterface).GetVirtualMethodTableInfoForKey(typeof(INullArrayCases));
int length = 10;
var __target = (delegate* unmanaged[MemberFunction]<void*, int, int**, int>)__vtable[11];
int* __span_native = null;
int __invokeRetVal = __target(__this, length, &__span_native);
Marshal.ThrowExceptionForHR(__invokeRetVal);
}
[Fact]
public void SpanNull_WithZeroLength_DoesNotCrash()
{
var testInterface = CreateTestInterface();
var (__this, __vtable) = ((IUnmanagedVirtualMethodTableProvider)testInterface).GetVirtualMethodTableInfoForKey(typeof(INullArrayCases));
int length = 0;
var __target = (delegate* unmanaged[MemberFunction]<void*, int, int**, int>)__vtable[11];
int* __span_native = null;
int __invokeRetVal = __target(__this, length, &__span_native);
Marshal.ThrowExceptionForHR(__invokeRetVal);
}
[Fact]
public void SpanNull_WithLargeLength_DoesNotCrash()
{
var testInterface = CreateTestInterface();
var (__this, __vtable) = ((IUnmanagedVirtualMethodTableProvider)testInterface).GetVirtualMethodTableInfoForKey(typeof(INullArrayCases));
int length = int.MaxValue;
var __target = (delegate* unmanaged[MemberFunction]<void*, int, int**, int>)__vtable[11];
int* __span_native = null;
int __invokeRetVal = __target(__this, length, &__span_native);
Marshal.ThrowExceptionForHR(__invokeRetVal);
}
[Fact]
public void SpanNonBlittable_Null_DoesNotCrash()
{
var testInterface = CreateTestInterface();
var (__this, __vtable) = ((IUnmanagedVirtualMethodTableProvider)testInterface).GetVirtualMethodTableInfoForKey(typeof(INullArrayCases));
int length = 10;
var __target = (delegate* unmanaged[MemberFunction]<void*, int, IntStructWrapper**, int>)__vtable[12];
IntStructWrapper* __span_native = null;
int __invokeRetVal = __target(__this, length, &__span_native);
Marshal.ThrowExceptionForHR(__invokeRetVal);
}
[Fact]
public void SpanValid_WorksNormally()
{
var testInterface = CreateTestInterface();
var span = new Span<int>(new int[5]);
testInterface.SpanNullCase(5, ref span);
Assert.Equal(0, span[0]);
Assert.Equal(5, span[1]);
Assert.Equal(10, span[2]);
Assert.Equal(15, span[3]);
Assert.Equal(20, span[4]);
}
[Fact]
public void SpanNonBlittableValid_WorksNormally()
{
var testInterface = CreateTestInterface();
var span = new Span<IntStructWrapper>(new IntStructWrapper[3]);
testInterface.SpanNonBlittableNullCase(3, ref span);
Assert.Equal(0, span[0].Value);
Assert.Equal(7, span[1].Value);
Assert.Equal(14, span[2].Value);
}
[Fact]
public void InputOnlyArray_Null_DoesNotCrash()
{
var testInterface = CreateTestInterface();
testInterface.InOnlyNullArray(10, null);
}
[Fact]
public void OutputOnlyArray_Null_DoesNotCrash()
{
var testInterface = CreateTestInterface();
testInterface.OutOnlyNullArray(10, null);
}
[Fact]
public void ReferenceArray_Null_DoesNotCrash()
{
var testInterface = CreateTestInterface();
testInterface.ReferenceArrayNullCase(10, null);
}
[Fact]
public void ReferenceArray_ValidArray_WorksNormally()
{
var testInterface = CreateTestInterface();
string[] array = new string[3];
testInterface.ReferenceArrayNullCase(3, array);
Assert.Equal(new string[] { "Item 0", "Item 1", "Item 2" }, array);
}
[Theory]
[InlineData(-1)]
[InlineData(0)]
[InlineData(1)]
[InlineData(100)]
public void NullArray_VariousLengths_DoesNotCrash(int length)
{
var testInterface = CreateTestInterface();
testInterface.SingleNullArrayWithLength(length, null);
}
}
}

View File

@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using SharedTypes.ComInterfaces;
using Xunit;
namespace ComInterfaceGenerator.Tests
{
public partial class INullableOutArrayTests
{
[Fact]
// Regression test for https://github.com/dotnet/runtime/issues/118135
public unsafe void NullableOutArray_Marshalling_Works()
{
// Arrange
INullableOutArray originalObject = new INullableOutArrayImpl();
ComWrappers cw = new StrategyBasedComWrappers();
nint ptr = cw.GetOrCreateComInterfaceForObject(originalObject, CreateComInterfaceFlags.None);
object obj = cw.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.None);
INullableOutArray throughInterface = (INullableOutArray)obj;
var (__this, __vtable) = ((IUnmanagedVirtualMethodTableProvider)throughInterface).GetVirtualMethodTableInfoForKey(typeof(INullableOutArray));
var __target = (delegate* unmanaged[MemberFunction]<void*, int, int*, int*, int>)__vtable[4];
int[] outputArray = new int[5];
fixed (int* __outputArray_native = &ArrayMarshaller<int, int>.ManagedToUnmanagedIn.GetPinnableReference(outputArray))
{
int hr = __target(__this, 5, __outputArray_native, null);
Marshal.ThrowExceptionForHR(hr);
}
}
}
}

View File

@ -112,6 +112,33 @@ namespace LibraryImportGenerator.IntegrationTests
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "return_duplicate_int_ptr_array")]
[return: MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
public static unsafe partial int*[] ReturnDuplicate(int*[] values, int numValues);
// Null array edge case methods
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "null_array_blittable_byval")]
public static partial int NullArrayBlittableByVal(int[] array, int length);
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "null_array_blittable_in")]
public static partial int NullArrayBlittableIn(in int[] array, int length);
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "null_array_blittable_ref")]
public static partial int NullArrayBlittableRef([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] ref int[] array, int length);
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "null_array_blittable_out")]
public static partial int NullArrayBlittableOut(int length, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] out int[] array);
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "null_array_nonblittable_byval")]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool NullArrayNonBlittableByVal(BoolStruct[] array, int length);
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "null_array_nonblittable_in")]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool NullArrayNonBlittableIn(in BoolStruct[] array, int length);
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "null_array_nonblittable_ref")]
public static partial void NullArrayNonBlittableRef([MarshalUsing(CountElementName = "length")] ref BoolStruct[] array, int length);
[LibraryImport(NativeExportsNE_Binary, EntryPoint = "null_array_nonblittable_out")]
public static partial void NullArrayNonBlittableOut(int length, [MarshalUsing(CountElementName = "length")] out BoolStruct[] array);
}
}
@ -836,5 +863,198 @@ namespace LibraryImportGenerator.IntegrationTests
Array.Reverse(chars);
return new string(chars);
}
[Fact]
public void NullArrayBlittable_ByVal_WithNonZeroLength()
{
// Test null array with non-zero length parameter - should not crash
int[] nullArray = null;
int result = NativeExportsNE.Arrays.NullArrayBlittableByVal(nullArray, 5);
Assert.Equal(-1, result); // Native method should return -1 for null array
}
[Fact]
public void NullArrayBlittable_ByVal_WithZeroLength()
{
// Test null array with zero length parameter - should work normally
int[] nullArray = null;
int result = NativeExportsNE.Arrays.NullArrayBlittableByVal(nullArray, 0);
Assert.Equal(-1, result); // Native method should return -1 for null array
}
[Fact]
public void NullArrayBlittable_In_WithNonZeroLength()
{
// Test null array with 'in' parameter and non-zero length
int[] nullArray = null;
int result = NativeExportsNE.Arrays.NullArrayBlittableIn(nullArray, 3);
Assert.Equal(-1, result); // Native method should return -1 for null array
}
[Fact]
public void NullArrayBlittable_In_WithZeroLength()
{
// Test null array with 'in' parameter and zero length
int[] nullArray = null;
int result = NativeExportsNE.Arrays.NullArrayBlittableIn(nullArray, 0);
Assert.Equal(-1, result); // Native method should return -1 for null array
}
[Fact]
public void NullArrayBlittable_Ref_WithNonZeroLength()
{
// Test null array with 'ref' parameter and non-zero length
int[] nullArray = null;
int result = NativeExportsNE.Arrays.NullArrayBlittableRef(ref nullArray, 4);
Assert.Equal(-1, result); // Native method should return -1 for null array
Assert.Null(nullArray); // Array should remain null
}
[Fact]
public void NullArrayBlittable_Ref_WithZeroLength()
{
// Test null array with 'ref' parameter and zero length
int[] nullArray = null;
int result = NativeExportsNE.Arrays.NullArrayBlittableRef(ref nullArray, 0);
Assert.Equal(-1, result); // Native method should return -1 for null array
Assert.Null(nullArray); // Array should remain null
}
[Fact]
public void NullArrayBlittable_Out_WithNonZeroLength()
{
// Test 'out' parameter that should set array to null with non-zero length
int result = NativeExportsNE.Arrays.NullArrayBlittableOut(3, out int[] array);
Assert.Equal(0, result); // Native method should return 0 when setting null
Assert.Null(array); // Array should be null
}
[Fact]
public void NullArrayBlittable_Out_WithZeroLength()
{
// Test 'out' parameter that should set array to null with zero length
int result = NativeExportsNE.Arrays.NullArrayBlittableOut(0, out int[] array);
Assert.Equal(0, result); // Native method should return 0 when setting null
Assert.Null(array); // Array should be null
}
[Fact]
public void NullArrayNonBlittable_ByVal_WithNonZeroLength()
{
// Test null array with non-blittable elements and non-zero length
BoolStruct[] nullArray = null;
bool result = NativeExportsNE.Arrays.NullArrayNonBlittableByVal(nullArray, 2);
Assert.False(result); // Native method should return false for null array
}
[Fact]
public void NullArrayNonBlittable_ByVal_WithZeroLength()
{
// Test null array with non-blittable elements and zero length
BoolStruct[] nullArray = null;
bool result = NativeExportsNE.Arrays.NullArrayNonBlittableByVal(nullArray, 0);
Assert.False(result); // Native method should return false for null array
}
[Fact]
public void NullArrayNonBlittable_In_WithNonZeroLength()
{
// Test null array with 'in' parameter, non-blittable elements, and non-zero length
BoolStruct[] nullArray = null;
bool result = NativeExportsNE.Arrays.NullArrayNonBlittableIn(nullArray, 1);
Assert.False(result); // Native method should return false for null array
}
[Fact]
public void NullArrayNonBlittable_In_WithZeroLength()
{
// Test null array with 'in' parameter, non-blittable elements, and zero length
BoolStruct[] nullArray = null;
bool result = NativeExportsNE.Arrays.NullArrayNonBlittableIn(nullArray, 0);
Assert.False(result); // Native method should return false for null array
}
[Fact]
public void NullArrayNonBlittable_Ref_WithNonZeroLength()
{
// Test null array with 'ref' parameter, non-blittable elements, and non-zero length
BoolStruct[] nullArray = null;
NativeExportsNE.Arrays.NullArrayNonBlittableRef(ref nullArray, 2);
Assert.Null(nullArray); // Array should remain null
}
[Fact]
public void NullArrayNonBlittable_Ref_WithZeroLength()
{
// Test null array with 'ref' parameter, non-blittable elements, and zero length
BoolStruct[] nullArray = null;
NativeExportsNE.Arrays.NullArrayNonBlittableRef(ref nullArray, 0);
Assert.Null(nullArray); // Array should remain null
}
[Fact]
public void NullArrayNonBlittable_Out_WithNonZeroLength()
{
// Test 'out' parameter that should set array to null with non-zero length
NativeExportsNE.Arrays.NullArrayNonBlittableOut(1, out BoolStruct[] array);
Assert.Null(array); // Array should be null
}
[Fact]
public void NullArrayNonBlittable_Out_WithZeroLength()
{
// Test 'out' parameter that should set array to null with zero length
NativeExportsNE.Arrays.NullArrayNonBlittableOut(0, out BoolStruct[] array);
Assert.Null(array); // Array should be null
}
[Fact]
public void ValidArrayBlittable_WithNullArrayMethods()
{
// Test that valid arrays work correctly with our null array methods
int[] validArray = new[] { 1, 2, 3, 4, 5 };
// Test ByVal with valid array
int result = NativeExportsNE.Arrays.NullArrayBlittableByVal(validArray, validArray.Length);
Assert.Equal(15, result); // Sum should be 1+2+3+4+5 = 15
// Test In with valid array
result = NativeExportsNE.Arrays.NullArrayBlittableIn(validArray, validArray.Length);
Assert.Equal(15, result); // Sum should be 1+2+3+4+5 = 15
// Test Ref with valid array
result = NativeExportsNE.Arrays.NullArrayBlittableRef(ref validArray, validArray.Length);
Assert.Equal(15, result); // Sum should be 1+2+3+4+5 = 15
Assert.NotNull(validArray); // Array should still be valid
}
[Fact]
public void ValidArrayNonBlittable_WithNullArrayMethods()
{
// Test that valid arrays work correctly with our null array non-blittable methods
BoolStruct[] validArray = new[]
{
new BoolStruct { b1 = true, b2 = true, b3 = true },
new BoolStruct { b1 = true, b2 = true, b3 = true }
};
// Test ByVal with valid array - should return true since all fields are true
bool result = NativeExportsNE.Arrays.NullArrayNonBlittableByVal(validArray, validArray.Length);
Assert.True(result);
// Test In with valid array - should return true since all fields are true
result = NativeExportsNE.Arrays.NullArrayNonBlittableIn(validArray, validArray.Length);
Assert.True(result);
// Test with array containing false values
BoolStruct[] mixedArray = new[]
{
new BoolStruct { b1 = true, b2 = true, b3 = true },
new BoolStruct { b1 = true, b2 = false, b3 = true } // b2 is false
};
result = NativeExportsNE.Arrays.NullArrayNonBlittableByVal(mixedArray, mixedArray.Length);
Assert.False(result); // Should return false due to b2 being false
}
}
}

View File

@ -440,5 +440,128 @@ namespace NativeExports
*numValues = newStrings.Count;
return res;
}
// Null array edge case methods for LibraryImportGenerator tests
[UnmanagedCallersOnly(EntryPoint = "null_array_blittable_byval")]
public static int NullArrayBlittableByVal(int* array, int length)
{
if (array == null)
{
return -1;
}
// If not null, sum the array elements
int sum = 0;
for (int i = 0; i < length; i++)
{
sum += array[i];
}
return sum;
}
[UnmanagedCallersOnly(EntryPoint = "null_array_blittable_in")]
public static int NullArrayBlittableIn(int** array, int length)
{
if (*array == null)
{
return -1;
}
// If not null, sum the array elements
int sum = 0;
for (int i = 0; i < length; i++)
{
sum += (*array)[i];
}
return sum;
}
[UnmanagedCallersOnly(EntryPoint = "null_array_blittable_ref")]
public static int NullArrayBlittableRef(int** array, int length)
{
if (*array == null)
{
return -1;
}
// If not null, sum the array elements
int sum = 0;
for (int i = 0; i < length; i++)
{
sum += (*array)[i];
}
return sum;
}
[UnmanagedCallersOnly(EntryPoint = "null_array_blittable_out")]
public static int NullArrayBlittableOut(int length, int** array)
{
// Always set the output array to null for testing purposes
*array = null;
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "null_array_nonblittable_byval")]
[DNNE.C99DeclCode("struct bool_struct;")]
public static byte NullArrayNonBlittableByVal([DNNE.C99Type("struct bool_struct*")] BoolStructMarshaller.BoolStructNative* array, int length)
{
if (array == null)
{
return 0; // false
}
// If not null, check if all members are true
for (int i = 0; i < length; i++)
{
BoolStruct managed = BoolStructMarshaller.ConvertToManaged(array[i]);
if (!managed.b1 || !managed.b2 || !managed.b3)
{
return 0; // false
}
}
return 1; // true
}
[UnmanagedCallersOnly(EntryPoint = "null_array_nonblittable_in")]
[DNNE.C99DeclCode("struct bool_struct;")]
public static byte NullArrayNonBlittableIn([DNNE.C99Type("struct bool_struct**")] BoolStructMarshaller.BoolStructNative** array, int length)
{
if (*array == null)
{
return 0; // false
}
// If not null, check if all members are true
for (int i = 0; i < length; i++)
{
BoolStruct managed = BoolStructMarshaller.ConvertToManaged((*array)[i]);
if (!managed.b1 || !managed.b2 || !managed.b3)
{
return 0; // false
}
}
return 1; // true
}
[UnmanagedCallersOnly(EntryPoint = "null_array_nonblittable_ref")]
[DNNE.C99DeclCode("struct bool_struct;")]
public static void NullArrayNonBlittableRef([DNNE.C99Type("struct bool_struct**")] BoolStructMarshaller.BoolStructNative** array, int length)
{
// For testing purposes, just check if array is null and leave it as is
if (*array == null)
{
return;
}
// If not null, we could do some operation, but for null testing we'll just return
}
[UnmanagedCallersOnly(EntryPoint = "null_array_nonblittable_out")]
[DNNE.C99DeclCode("struct bool_struct;")]
public static void NullArrayNonBlittableOut(int length, [DNNE.C99Type("struct bool_struct**")] BoolStructMarshaller.BoolStructNative** array)
{
// Always set the output array to null for testing purposes
*array = null;
}
}
}

View File

@ -0,0 +1,204 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace SharedTypes.ComInterfaces
{
[GeneratedComInterface]
[Guid("F8A2C5D1-9B7E-4A3C-8F5D-2E1B9C4A7F6E")]
internal partial interface INullArrayCases
{
// Basic case: single null array with non-zero length
void SingleNullArrayWithLength(
int length,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] int[]? array);
// Multiple arrays sharing same length, some null, some not
void MultipleArraysSharedLength(
int length,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] int[]? array1,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] int[]? array2,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] int[]? array3);
// Non-blittable types with null arrays
void NonBlittableNullArray(
int length,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] IntStructWrapper[]? array);
// Zero-length case (should handle gracefully)
void ZeroLengthArray(
int length, // Will be 0
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] int[]? array);
// Large length with null array (potential overflow/crash scenario)
void LargeLengthNullArray(
int length, // Will be int.MaxValue
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] int[]? array);
// Different parameter directions
void InOnlyNullArray(
int length,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In] int[]? array);
void OutOnlyNullArray(
int length,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out] int[]? array);
// Reference arrays with null (different from value type arrays)
void ReferenceArrayNullCase(
int length,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 0), In, Out] string[]? array);
// Span<T> with null cases (ContiguousCollectionMarshaller)
void SpanNullCase(
int length,
[MarshalUsing(CountElementName = nameof(length))] ref Span<int> span);
// Span<T> with non-blittable types
void SpanNonBlittableNullCase(
int length,
[MarshalUsing(CountElementName = nameof(length))] ref Span<IntStructWrapper> span);
}
[GeneratedComClass]
internal partial class INullArrayCasesImpl : INullArrayCases
{
public void SingleNullArrayWithLength(int length, int[]? array)
{
// Should handle null array gracefully regardless of length
if (array != null)
{
for (int i = 0; i < Math.Min(length, array.Length); i++)
{
array[i] = i * 2;
}
}
}
public void MultipleArraysSharedLength(int length, int[]? array1, int[]? array2, int[]? array3)
{
// Should only process non-null arrays
if (array1 != null)
{
for (int i = 0; i < length; i++)
{
array1[i] = i;
}
}
if (array2 != null)
{
for (int i = 0; i < length; i++)
{
array2[i] = i * 10;
}
}
if (array3 != null)
{
for (int i = 0; i < length; i++)
{
array3[i] = i * 100;
}
}
}
public void NonBlittableNullArray(int length, IntStructWrapper[]? array)
{
if (array != null)
{
for (int i = 0; i < length; i++)
{
array[i] = new IntStructWrapper { Value = i * 3 };
}
}
}
public void ZeroLengthArray(int length, int[]? array)
{
// Should handle zero length gracefully
if (array != null && length > 0)
{
for (int i = 0; i < length; i++)
{
array[i] = 42;
}
}
}
public void LargeLengthNullArray(int length, int[]? array)
{
// Should not crash or allocate massive amounts when array is null
if (array != null)
{
for (int i = 0; i < length; i++)
{
array[i] = i;
}
}
}
public void InOnlyNullArray(int length, int[]? array)
{
// Input-only: just read from array if not null
if (array != null)
{
int sum = 0;
for (int i = 0; i < length; i++)
{
sum += array[i];
}
// Could store sum somewhere, but for test we just ensure no crash
}
}
public void OutOnlyNullArray(int length, int[]? array)
{
// Output-only: write to array if not null
if (array != null)
{
for (int i = 0; i < length; i++)
{
array[i] = i + 1000;
}
}
}
public void ReferenceArrayNullCase(int length, string[]? array)
{
if (array != null)
{
for (int i = 0; i < length; i++)
{
array[i] = $"Item {i}";
}
}
}
public void SpanNullCase(int length, ref Span<int> span)
{
// Should handle empty/default span gracefully regardless of length
if (!span.IsEmpty)
{
for (int i = 0; i < length; i++)
{
span[i] = i * 5;
}
}
}
public void SpanNonBlittableNullCase(int length, ref Span<IntStructWrapper> span)
{
// Should handle empty/default span gracefully regardless of length
if (!span.IsEmpty)
{
for (int i = 0; i < length; i++)
{
span[i] = new IntStructWrapper { Value = i * 7 };
}
}
}
}
}

View File

@ -0,0 +1,63 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace SharedTypes.ComInterfaces
{
[GeneratedComInterface]
[Guid("5A9D3ED6-CC17-4FB9-8F82-0070489B7213")]
internal partial interface INullableOutArray
{
void Method(int size, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] IntStructWrapper[]? array, [MarshalAs(UnmanagedType.Bool)] bool passedNull);
// Definition
void M(
int bufferSize,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] IntStructWrapper[]? buffer1,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), In, Out] IntStructWrapper[]? buffer2);
}
[GeneratedComClass]
internal partial class INullableOutArrayImpl : INullableOutArray
{
public void M(int bufferSize, IntStructWrapper[]? buffer1, IntStructWrapper[]? buffer2)
{
if (buffer1 is not null)
{
for (int i = 0; i < bufferSize; i++)
{
buffer1[i] = new() { Value = i };
}
}
if (buffer2 is not null)
{
for (int i = 0; i < bufferSize; i++)
{
buffer2[i] = new() { Value = i };
}
}
}
public void Method(int size, IntStructWrapper[]? array, bool passedNull)
{
if (passedNull)
{
if (array is not null)
{
throw new ArgumentException("Expected array to be null when passedNull is true.");
}
return;
}
if (size == 0 || array is null)
{
return;
}
for (int i = 0; i < size; i++)
{
array[i] = new() { Value = i };
}
}
}
}