Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@
<Compile Include="System\Runtime\InteropServices\Marshal\Common\CommonTypes.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\Common\COMWrappersImpl.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\Common\CommonTypes.Windows.cs" Condition="'$(TargetPlatformIdentifier)' == 'windows'" />
<Compile Include="System\Runtime\InteropServices\Marshal\Common\Variant.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\ReadWrite\ByteTests.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\ReadWrite\Int16Tests.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\ReadWrite\Int32Tests.cs" />
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.InteropServices.Tests.Common;
using Xunit;

Expand Down Expand Up @@ -108,8 +109,7 @@ public static IEnumerable<object[]> GetNativeVariantForObject_InvalidArrayType_T
[MemberData(nameof(GetNativeVariantForObject_InvalidArrayType_TestData))]
public void GetNativeVariantForObject_InvalidArrayType_ThrowsInvalidCastException(object obj)
{
Variant v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
Assert.Throws<InvalidCastException>(() => Marshal.GetNativeVariantForObject(obj, pNative));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Drawing;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.InteropServices.Tests.Common;
using Xunit;

Expand Down Expand Up @@ -109,22 +110,21 @@ public static IEnumerable<object[]> GetNativeVariantForObject_NonRoundtrippingPr
[MemberData(nameof(GetNativeVariantForObject_NonRoundtrippingPrimitives_TestData))]
public void GetNativeVariantForObject_ValidObject_Success(object primitive, VarEnum expectedVarType, IntPtr expectedValue, object expectedRoundtripValue)
{
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong. I don't think there is any requirement that AllocHGlobal returns zero'd memory. I don't think we really need an allocation at all. Why not just pass a pointer to the variant on the stack?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. @copilot please use a ComVariant local here instead of allocating with AllocHGlobal/FreeHGlobal

try
{
Marshal.GetNativeVariantForObject(primitive, pNative);
Comment on lines +113 to 116
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pNative holds a native VARIANT produced by Marshal.GetNativeVariantForObject. Some inputs in this test data produce VARIANTs that own native resources (e.g., VT_BSTR, VT_ARRAY/SAFEARRAY, COM interface pointers), but the buffer is later freed without clearing the VARIANT, which can leak those resources across the test run. Since this test now uses ComVariant, consider clearing the VARIANT stored at pNative (e.g., via an unsafe cast to ComVariant* and calling Dispose()) before freeing the backing buffer; make sure to guard the dispose so it only runs after GetNativeVariantForObject succeeds.

Copilot uses AI. Check for mistakes.

Variant result = Marshal.PtrToStructure<Variant>(pNative);
Assert.Equal(expectedVarType, (VarEnum)result.vt);
ComVariant result = Marshal.PtrToStructure<ComVariant>(pNative);
Assert.Equal(expectedVarType, result.VarType);
if (expectedValue != (IntPtr)(-1))
{
Assert.Equal(expectedValue, result.bstrVal);
Assert.Equal(expectedValue, result.GetRawDataRef<IntPtr>());
}
else
{
Assert.NotEqual((IntPtr)(-1), result.bstrVal);
Assert.NotEqual(IntPtr.Zero, result.bstrVal);
Assert.NotEqual((IntPtr)(-1), result.GetRawDataRef<IntPtr>());
Assert.NotEqual(IntPtr.Zero, result.GetRawDataRef<IntPtr>());
}

// Make sure it roundtrips.
Expand All @@ -141,24 +141,23 @@ public void GetNativeVariantForObject_ValidObject_Success(object primitive, VarE
[InlineData("99")]
public void GetNativeVariantForObject_String_Success(string obj)
{
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
Marshal.GetNativeVariantForObject(obj, pNative);

Variant result = Marshal.PtrToStructure<Variant>(pNative);
ComVariant result = Marshal.PtrToStructure<ComVariant>(pNative);
try
{
Assert.Equal(VarEnum.VT_BSTR, (VarEnum)result.vt);
Assert.Equal(obj, Marshal.PtrToStringBSTR(result.bstrVal));
Assert.Equal(VarEnum.VT_BSTR, result.VarType);
Assert.Equal(obj, Marshal.PtrToStringBSTR(result.GetRawDataRef<IntPtr>()));

object o = Marshal.GetObjectForNativeVariant(pNative);
Assert.Equal(obj, o);
}
finally
{
Marshal.FreeBSTR(result.bstrVal);
Marshal.FreeBSTR(result.GetRawDataRef<IntPtr>());
}
}
finally
Expand All @@ -171,8 +170,7 @@ public void GetNativeVariantForObject_String_Success(string obj)
public unsafe void GetNativeVariantForObject_Guid_Success()
{
var guid = new Guid("0DD3E51B-3162-4D13-B906-030F402C5BA2");
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
if (PlatformDetection.IsWindowsNanoServer)
Expand All @@ -183,12 +181,12 @@ public unsafe void GetNativeVariantForObject_Guid_Success()
{
Marshal.GetNativeVariantForObject(guid, pNative);

Variant result = Marshal.PtrToStructure<Variant>(pNative);
Assert.Equal(VarEnum.VT_RECORD, (VarEnum)result.vt);
Assert.NotEqual(nint.Zero, result.pRecInfo); // We should have an IRecordInfo instance.
ComVariant result = Marshal.PtrToStructure<ComVariant>(pNative);
Assert.Equal(VarEnum.VT_RECORD, result.VarType);
Assert.NotEqual(nint.Zero, result.GetRawDataRef<Record>()._recordInfo); // We should have an IRecordInfo instance.
Comment on lines 182 to +186
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the VT_RECORD case, the VARIANT typically owns both the record data and an IRecordInfo COM pointer. This test reads the pointers but doesn’t clear the VARIANT afterwards, so the record allocation/COM reference can leak. Consider disposing/clearing the VARIANT stored at pNative (e.g., ((ComVariant*)pNative)->Dispose() on Windows) once assertions are complete; ensure this is skipped on the NanoServer/exception path where the VARIANT may not have been initialized.

Copilot uses AI. Check for mistakes.

var expectedBytes = new ReadOnlySpan<byte>(guid.ToByteArray());
var actualBytes = new ReadOnlySpan<byte>((void*)result.bstrVal, expectedBytes.Length);
var actualBytes = new ReadOnlySpan<byte>((void*)result.GetRawDataRef<Record>()._record, expectedBytes.Length);
Assert.Equal(expectedBytes, actualBytes);

object o = Marshal.GetObjectForNativeVariant(pNative);
Expand All @@ -205,15 +203,14 @@ public unsafe void GetNativeVariantForObject_Guid_Success()
[InlineData(3.14)]
public unsafe void GetNativeVariantForObject_Double_Success(double obj)
{
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
Marshal.GetNativeVariantForObject(obj, pNative);

Variant result = Marshal.PtrToStructure<Variant>(pNative);
Assert.Equal(VarEnum.VT_R8, (VarEnum)result.vt);
Assert.Equal(*((ulong*)&obj), *((ulong*)&result.bstrVal));
ComVariant result = Marshal.PtrToStructure<ComVariant>(pNative);
Assert.Equal(VarEnum.VT_R8, result.VarType);
Assert.Equal(*((ulong*)&obj), result.GetRawDataRef<ulong>());

object o = Marshal.GetObjectForNativeVariant(pNative);
Assert.Equal(obj, o);
Expand All @@ -228,15 +225,14 @@ public unsafe void GetNativeVariantForObject_Double_Success(double obj)
[InlineData(3.14f)]
public unsafe void GetNativeVariantForObject_Float_Success(float obj)
{
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
Marshal.GetNativeVariantForObject(obj, pNative);

Variant result = Marshal.PtrToStructure<Variant>(pNative);
Assert.Equal(VarEnum.VT_R4, (VarEnum)result.vt);
Assert.Equal(*((uint*)&obj), *((uint*)&result.bstrVal));
ComVariant result = Marshal.PtrToStructure<ComVariant>(pNative);
Assert.Equal(VarEnum.VT_R4, result.VarType);
Assert.Equal(*((uint*)&obj), result.GetRawDataRef<uint>());

object o = Marshal.GetObjectForNativeVariant(pNative);
Assert.Equal(obj, o);
Expand Down Expand Up @@ -279,8 +275,7 @@ public void GetNativeVariantForObject_GenericObject_ThrowsArgumentException(obje
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBuiltInComEnabled))]
public void GetNativeVariant_InvalidArray_ThrowsSafeArrayTypeMismatchException()
{
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
Assert.Throws<SafeArrayTypeMismatchException>(() => Marshal.GetNativeVariantForObject(new int[][] { }, pNative));
Expand All @@ -302,8 +297,7 @@ public static IEnumerable<object[]> GetNativeVariant_VariantWrapper_TestData()
[MemberData(nameof(GetNativeVariant_VariantWrapper_TestData))]
public void GetNativeVariant_VariantWrapper_ThrowsArgumentException(object obj)
{
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
AssertExtensions.Throws<ArgumentException>(null, () => Marshal.GetNativeVariantForObject(obj, pNative));
Expand All @@ -328,8 +322,7 @@ public static IEnumerable<object[]> GetNativeVariant_HandleObject_TestData()
[MemberData(nameof(GetNativeVariant_HandleObject_TestData))]
public void GetNativeVariant_HandleObject_ThrowsArgumentException(object obj)
{
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
AssertExtensions.Throws<ArgumentException>(null, () => Marshal.GetNativeVariantForObject(obj, pNative));
Expand All @@ -346,8 +339,7 @@ public static void GetNativeVariantForObject_CantCastToObject_ThrowsInvalidCastE
{
// While GetNativeVariantForObject supports taking chars, GetObjectForNativeVariant will
// never return a char. The internal type is ushort, as mentioned above.
var v = new Variant();
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf(v));
IntPtr pNative = Marshal.AllocHGlobal(Marshal.SizeOf<ComVariant>());
try
{
Marshal.GetNativeVariantForObject<char>('a', pNative);
Expand All @@ -372,6 +364,13 @@ public enum UInt16Enum : ushort { Value1, Value2 }
public enum UInt32Enum : uint { Value1, Value2 }
public enum UInt64Enum : ulong { Value1, Value2 }

[StructLayout(LayoutKind.Sequential)]
private struct Record
{
public nint _record;
public nint _recordInfo;
}

public class FakeSafeHandle : SafeHandle
{
public FakeSafeHandle() : base(IntPtr.Zero, false) { }
Expand Down
Loading