Skip to content

Commit 7b29526

Browse files
[release/10.0.1xx] Source code updates from dotnet/winforms (#3079)
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
1 parent df6b2d5 commit 7b29526

File tree

8 files changed

+189
-28
lines changed

8 files changed

+189
-28
lines changed

src/source-manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@
133133
"commitSha": "da5c1d5aea74c2cedb284c4fe7b3457b47958914"
134134
},
135135
{
136-
"barId": 288278,
136+
"barId": 288382,
137137
"path": "winforms",
138138
"remoteUri": "https://github.com/dotnet/winforms",
139-
"commitSha": "93e65c061f2b41186b66dbfe4330865b5af9dfdc"
139+
"commitSha": "10bf1127895645391048768f8c31181fd5d6a8fa"
140140
},
141141
{
142142
"barId": 287944,

src/winforms/src/Common/tests/TestUtilities/ReflectionHelper.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,43 @@ private static bool IsPublicNonAbstract<T>(Type type)
4747
{
4848
return !type.IsAbstract && type.IsPublic && typeof(T).IsAssignableFrom(type);
4949
}
50+
51+
/// <summary>
52+
/// Gets a nested type, and if it's a generic type definition, makes it a full type using the parent type's generic arguments.
53+
/// </summary>
54+
/// <param name="parentType">The parent type.</param>
55+
/// <param name="nestedTypeName">The name of the nested type.</param>
56+
/// <param name="genericTypes">Additional nested type parameters, if any.</param>
57+
/// <returns>Nested types.</returns>
58+
/// <exception cref="ArgumentException">Could not find the <paramref name="nestedTypeName"/>.</exception>
59+
/// <exception cref="NotImplementedException">An additional case still needs implemented.</exception>
60+
public static Type GetFullNestedType(
61+
this Type parentType,
62+
string nestedTypeName,
63+
params Span<Type> genericTypes)
64+
{
65+
Type nestedType = parentType.GetNestedType(nestedTypeName, BindingFlags.Public | BindingFlags.NonPublic)
66+
?? throw new ArgumentException($"Could not find {nestedTypeName} in {parentType.Name}");
67+
68+
if (!nestedType.IsTypeDefinition)
69+
{
70+
return nestedType;
71+
}
72+
73+
if (parentType.IsGenericType)
74+
{
75+
Type[] parentTypes = parentType.GenericTypeArguments;
76+
Type[] nestedTypes = nestedType.GenericTypeArguments;
77+
78+
if (nestedTypes.Length == 0)
79+
{
80+
// Only the parent types are needed.
81+
Type fullType = nestedType.MakeGenericType(parentTypes);
82+
return fullType;
83+
}
84+
}
85+
86+
// Implementing the other cases is relatively trivial, leaving them until we have concrete usage.
87+
throw new NotImplementedException("Implement other cases as they occur");
88+
}
5089
}

src/winforms/src/Common/tests/TestUtilities/TestAccessor.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,16 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args,
133133
if (methodInfo is null)
134134
return false;
135135

136-
result = methodInfo.Invoke(_instance, args);
136+
try
137+
{
138+
result = methodInfo.Invoke(_instance, args);
139+
}
140+
catch (TargetInvocationException ex) when (ex.InnerException is not null)
141+
{
142+
// Unwrap the inner exception to make it easier for callers to handle.
143+
throw ex.InnerException;
144+
}
145+
137146
return true;
138147
}
139148

src/winforms/src/Common/tests/TestUtilities/TestAccessors.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ public static partial class TestAccessors
4646
/// Assert.Equal(version2, accessor.Parse("4.1")));
4747
/// ]]>
4848
/// </code>
49+
/// <para>
50+
/// When attempting to get nested private types that are generic (nested types in a generic type
51+
/// are always generic, and inherit the type specifiers of the the parent type), use the extension
52+
/// <see cref="ReflectionHelper.GetFullNestedType(Type, string, Span{Type})"/> to get a fully
53+
/// instantiated type for the nested type, then pass that Type to this method.
54+
/// </para>
4955
/// </remarks>
5056
public static ITestAccessor TestAccessor(this object instanceOrType)
5157
{

src/winforms/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.ComponentModel;
45
using System.Reflection.Metadata;
56
using System.Runtime.InteropServices;
67
using System.Runtime.InteropServices.ComTypes;
@@ -146,7 +147,7 @@ private static unsafe MemoryStream ReadByteStreamFromHGLOBAL(HGLOBAL hglobal, ou
146147

147148
try
148149
{
149-
int size = (int)PInvokeCore.GlobalSize(hglobal);
150+
int size = checked((int)PInvokeCore.GlobalSize(hglobal));
150151
byte[] bytes = GC.AllocateUninitializedArray<byte>(size);
151152
Marshal.Copy((nint)buffer, bytes, 0, size);
152153
int index = 0;
@@ -169,28 +170,76 @@ private static unsafe MemoryStream ReadByteStreamFromHGLOBAL(HGLOBAL hglobal, ou
169170

170171
private static unsafe string ReadStringFromHGLOBAL(HGLOBAL hglobal, bool unicode)
171172
{
172-
string? stringData = null;
173-
174173
void* buffer = PInvokeCore.GlobalLock(hglobal);
174+
if (buffer is null)
175+
{
176+
throw new Win32Exception();
177+
}
178+
179+
// https://learn.microsoft.com/windows/win32/dataxchg/standard-clipboard-formats
180+
// https://learn.microsoft.com/windows/win32/shell/clipboard#cfstr_filename
181+
//
182+
// CF_TEXT, CF_OEMTEXT, CF_UNICODETEXT, and CFSTR_FILENAME are supposed to have a null terminator.
183+
// If we cannot find one in the buffer, assume it is corrupted and return an empty string.
184+
//
185+
// Can't find the explicit docs for CF_RTF, but we've always treated it as null terminated.
186+
// The RichText control itself null terminates but looks like it doesn't require it.
187+
// Given our prior and "normal" behavior, we'll continue to expect a null terminator.
188+
175189
try
176190
{
177-
stringData = unicode ? new string((char*)buffer) : new string((sbyte*)buffer);
191+
int size = checked((int)PInvokeCore.GlobalSize(hglobal));
192+
if (size == 0)
193+
{
194+
throw new Win32Exception();
195+
}
196+
197+
if (unicode)
198+
{
199+
ReadOnlySpan<char> chars = new((char*)buffer, size / sizeof(char));
200+
int nullIndex = chars.IndexOf('\0');
201+
if (nullIndex < 0)
202+
{
203+
// Malformed, return empty string.
204+
return string.Empty;
205+
}
206+
207+
chars = chars[..nullIndex];
208+
return new string(chars);
209+
}
210+
else
211+
{
212+
ReadOnlySpan<byte> bytes = new((byte*)buffer, size);
213+
int nullIndex = bytes.IndexOf((byte)0);
214+
if (nullIndex < 0)
215+
{
216+
// Malformed, return empty string.
217+
return string.Empty;
218+
}
219+
220+
return new string((sbyte*)buffer, 0, nullIndex);
221+
}
178222
}
179223
finally
180224
{
181225
PInvokeCore.GlobalUnlock(hglobal);
182226
}
183-
184-
return stringData;
185227
}
186228

187229
private static unsafe string ReadUtf8StringFromHGLOBAL(HGLOBAL hglobal)
188230
{
189231
void* buffer = PInvokeCore.GlobalLock(hglobal);
232+
if (buffer is null)
233+
{
234+
throw new Win32Exception();
235+
}
236+
190237
try
191238
{
192-
int size = (int)PInvokeCore.GlobalSize(hglobal);
193-
return Encoding.UTF8.GetString((byte*)buffer, size - 1);
239+
int size = checked((int)PInvokeCore.GlobalSize(hglobal));
240+
return size == 0
241+
? throw new Win32Exception()
242+
: Encoding.UTF8.GetString((byte*)buffer, size - 1);
194243
}
195244
finally
196245
{

src/winforms/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeToManagedAdapterTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.ComponentModel;
45
using System.Formats.Nrbf;
56
using System.Private.Windows.BinaryFormat;
67
using System.Text.Json;
78
using Windows.Win32;
9+
using Windows.Win32.Foundation;
810
using Windows.Win32.System.Com;
11+
using Windows.Win32.System.Memory;
912

1013
using Composition = System.Private.Windows.Ole.Composition<
1114
System.Private.Windows.Ole.MockOleServices<System.Private.Windows.Ole.NativeToManagedAdapterTests>,
@@ -108,4 +111,74 @@ public void GetData_CustomType_BinaryFormattedJson_AsSerializationRecord()
108111
composition.TryGetData(nameof(NativeToManagedAdapterTests), out SerializationRecord? data).Should().BeTrue();
109112
data!.TypeName.AssemblyQualifiedName.Should().Be("System.Private.Windows.JsonData, System.Private.Windows.VirtualJson");
110113
}
114+
115+
[Theory]
116+
[BoolData]
117+
public void ReadStringFromHGLOBAL_InvalidHGLOBAL_Throws(bool unicode)
118+
{
119+
Type type = typeof(Composition).GetFullNestedType("NativeToManagedAdapter");
120+
121+
Action action = () =>
122+
{
123+
string result = type.TestAccessor().Dynamic.ReadStringFromHGLOBAL(HGLOBAL.Null, unicode);
124+
};
125+
126+
action.Should().Throw<Win32Exception>().And.HResult.Should().Be((int)HRESULT.E_FAIL);
127+
}
128+
129+
[Theory]
130+
[BoolData]
131+
public void ReadStringFromHGLOBAL_NoTerminator_ReturnsEmptyString(bool unicode)
132+
{
133+
Type type = typeof(Composition).GetFullNestedType("NativeToManagedAdapter");
134+
135+
// There is no way to create a zero-length HGLOBAL, GlobalAlloc will always allocate at least some memory.
136+
HGLOBAL global = PInvokeCore.GlobalAlloc(GLOBAL_ALLOC_FLAGS.GMEM_MOVEABLE, 6);
137+
nuint size = PInvokeCore.GlobalSize(global);
138+
139+
try
140+
{
141+
using (GlobalBuffer buffer = new(global, (uint)size))
142+
{
143+
Span<byte> span = buffer.AsSpan();
144+
// Fill spaces or daggers
145+
span.Fill(0x20);
146+
}
147+
148+
string result = type.TestAccessor().Dynamic.ReadStringFromHGLOBAL(global, unicode);
149+
result.Should().BeEmpty();
150+
}
151+
finally
152+
{
153+
PInvokeCore.GlobalFree(global);
154+
}
155+
}
156+
157+
[Theory]
158+
[BoolData]
159+
public void ReadStringFromHGLOBAL_Terminator_ReturnsString(bool unicode)
160+
{
161+
Type type = typeof(Composition).GetFullNestedType("NativeToManagedAdapter");
162+
163+
// There is no way to create a zero-length HGLOBAL, GlobalAlloc will always allocate at least some memory.
164+
HGLOBAL global = PInvokeCore.GlobalAlloc(GLOBAL_ALLOC_FLAGS.GMEM_MOVEABLE | GLOBAL_ALLOC_FLAGS.GMEM_ZEROINIT, 6);
165+
nuint size = PInvokeCore.GlobalSize(global);
166+
167+
try
168+
{
169+
using (GlobalBuffer buffer = new(global, (uint)size))
170+
{
171+
Span<byte> span = buffer.AsSpan();
172+
// Fill spaces or daggers, leave the last two bytes as zero
173+
span[..^2].Fill(0x20);
174+
}
175+
176+
string result = type.TestAccessor().Dynamic.ReadStringFromHGLOBAL(global, unicode);
177+
result.Should().NotBeEmpty();
178+
}
179+
finally
180+
{
181+
PInvokeCore.GlobalFree(global);
182+
}
183+
}
111184
}

src/winforms/src/System.Windows.Forms.Design/tests/UnitTests/System/Windows/Forms/Design/ListViewGroupCollectionEditorTests.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.ComponentModel;
5-
using System.Reflection;
65
using Moq;
76

87
namespace System.Windows.Forms.Design.Tests;
@@ -56,14 +55,4 @@ public void CreateInstance_CreatesListViewGroupWithUniqueName()
5655
result?.Name.Should().StartWith("ListViewGroup");
5756
result?.GetType().Should().Be(typeof(ListViewGroup));
5857
}
59-
60-
[Fact]
61-
public void CreateInstance_ThrowsException_WhenEditValueIsNull()
62-
{
63-
_mockEditor.Object.TestAccessor().Dynamic._editValue = null;
64-
65-
Action action = () => _mockEditor.Object.TestAccessor().Dynamic.CreateInstance(typeof(ListViewGroup));
66-
67-
action.Should().Throw<TargetInvocationException>();
68-
}
6958
}

src/winforms/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataGridViewLinkCellTests.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Drawing;
5-
using System.Reflection;
65

76
namespace System.Windows.Forms.Tests;
87

@@ -655,7 +654,7 @@ public void Paint_ThrowsArgumentNullException_WhenCellStyleIsNull()
655654
using Bitmap bitmap = new(10, 10);
656655
using Graphics graphics = Graphics.FromImage(bitmap);
657656

658-
TargetInvocationException ex = ((Action)(() =>
657+
((Action)(() =>
659658
_cell.TestAccessor().Dynamic.Paint(
660659
graphics,
661660
new Rectangle(0, 0, 10, 10),
@@ -669,9 +668,6 @@ public void Paint_ThrowsArgumentNullException_WhenCellStyleIsNull()
669668
null,
670669
DataGridViewPaintParts.All
671670
)
672-
)).Should().Throw<TargetInvocationException>().Subject.First();
673-
674-
ex.InnerException.Should().BeOfType<ArgumentNullException>();
675-
ex.InnerException!.Message.Should().Contain("cellStyle");
671+
)).Should().Throw<ArgumentNullException>().And.Message.Should().Contain("cellStyle");
676672
}
677673
}

0 commit comments

Comments
 (0)