Skip to content

Commit d531fc7

Browse files
authored
Fix bug when multiple Span-params share a CountParamIndex and one param is null (#1575)
1 parent df226e9 commit d531fc7

File tree

3 files changed

+71
-7
lines changed

3 files changed

+71
-7
lines changed

src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ private static ExpressionSyntax GetSpanLength(ExpressionSyntax span, bool isRefT
2222
ConditionalAccessExpression(span, IdentifierName(nameof(Span<int>.Length))),
2323
LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)))) : MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, span, IdentifierName(nameof(Span<int>.Length)));
2424

25+
private static ExpressionSyntax GetIsSpanEmpty(ExpressionSyntax span, bool isRefType) => isRefType ?
26+
ParenthesizedExpression(BinaryExpression(
27+
SyntaxKind.EqualsExpression,
28+
span,
29+
LiteralExpression(SyntaxKind.NullLiteralExpression))) :
30+
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, span, IdentifierName(nameof(Span<int>.IsEmpty)));
31+
2532
private ExpressionSyntax GetIntPtrFromTypeDef(ExpressionSyntax typedefValue, TypeHandleInfo typeDefTypeInfo)
2633
{
2734
ExpressionSyntax intPtrValue = typedefValue;
@@ -1017,31 +1024,54 @@ bool TryHandleCountParam(TypeSyntax elementType, bool nullableSource)
10171024
}
10181025
}
10191026

1027+
ExpressionSyntax sizeArgExpression;
10201028
if (lengthParamUsedBy.TryGetValue(countParamIndex.Value, out int userIndex))
10211029
{
1030+
bool origNameIsRefType = remainsRefType;
1031+
bool otherUserNameIsRefType = parameters[userIndex].Type is ArrayTypeSyntax;
1032+
10221033
// Multiple array parameters share a common 'length' parameter.
10231034
// Since we're making this a little less obvious, add a quick if check in the helper method
10241035
// that enforces that all such parameters have a common span length.
10251036
ExpressionSyntax otherUserName = IdentifierName(parameters[userIndex].Identifier.ValueText);
1037+
1038+
// Only enforce length equality when both spans are non-empty.
1039+
ExpressionSyntax otherNotEmpty = PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, GetIsSpanEmpty(otherUserName, otherUserNameIsRefType));
1040+
ExpressionSyntax origNotEmpty = PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, GetIsSpanEmpty(origName, origNameIsRefType));
1041+
ExpressionSyntax lengthsNotEqual = BinaryExpression(
1042+
SyntaxKind.NotEqualsExpression,
1043+
GetSpanLength(otherUserName, otherUserNameIsRefType),
1044+
GetSpanLength(origName, origNameIsRefType));
1045+
ExpressionSyntax condition = BinaryExpression(SyntaxKind.LogicalAndExpression, BinaryExpression(SyntaxKind.LogicalAndExpression, otherNotEmpty, origNotEmpty), lengthsNotEqual);
10261046
leadingStatements.Add(IfStatement(
1027-
BinaryExpression(
1028-
SyntaxKind.NotEqualsExpression,
1029-
GetSpanLength(otherUserName, parameters[userIndex].Type is ArrayTypeSyntax),
1030-
GetSpanLength(origName, remainsRefType)),
1047+
condition,
10311048
ThrowStatement(ObjectCreationExpression(IdentifierName(nameof(ArgumentException))).WithArgumentList(ArgumentList()))));
1049+
1050+
// Also we need to compound the size argument so that if one of the spans was empty, we pass the non-zero one.
1051+
sizeArgExpression = arguments[countParamIndex.Value].Expression;
1052+
if (sizeArgExpression is CastExpressionSyntax { Expression: ExpressionSyntax castedExpression })
1053+
{
1054+
// Unwrap the cast so we can simplify the logic
1055+
sizeArgExpression = castedExpression;
1056+
}
1057+
1058+
sizeArgExpression = ParenthesizedExpression(ConditionalExpression(GetIsSpanEmpty(origName, origNameIsRefType), sizeArgExpression, GetSpanLength(origName, origNameIsRefType)));
10321059
}
10331060
else
10341061
{
10351062
lengthParamUsedBy.Add(countParamIndex.Value, paramIndex);
1063+
1064+
sizeArgExpression = GetSpanLength(origName, remainsRefType);
10361065
}
10371066

1038-
ExpressionSyntax sizeArgExpression = GetSpanLength(origName, remainsRefType);
1067+
// Always wrap the sizeArgExpression in CastExpression if needed.
10391068
if (!(parameters[countParamIndex.Value].Type is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.IntKeyword } }))
10401069
{
10411070
sizeArgExpression = CastExpression(parameters[countParamIndex.Value].Type!, sizeArgExpression);
10421071
}
10431072

10441073
arguments[countParamIndex.Value] = Argument(sizeArgExpression);
1074+
10451075
return true;
10461076
}
10471077

test/GenerationSandbox.BuildTask.Tests/NativeMethods.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,8 @@ WbemLocator
7171
CLSCTX
7272
CoSetProxyBlanket
7373
RPC_C_AUTHN_LEVEL
74-
RPC_C_IMP_LEVEL
74+
RPC_C_IMP_LEVEL
75+
InitializeProcThreadAttributeList
76+
UpdateProcThreadAttribute
77+
DeleteProcThreadAttributeList
78+
PROC_THREAD_ATTRIBUTE_MACHINE_TYPE

test/GenerationSandbox.BuildTask.Tests/SpanOverloadTests.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
using System.Globalization;
55
using System.Runtime.InteropServices;
6+
using Windows.Win32;
67
using Windows.Win32.Storage.FileSystem;
8+
using Windows.Win32.System.Threading;
79
using static Windows.Win32.PInvoke;
810

9-
#pragma warning disable SA1201, CS0649
11+
#pragma warning disable SA1201, CS0649, SA1202
1012

1113
namespace GenerationSandbox.BuildTask.Tests;
1214

@@ -58,4 +60,32 @@ descPtr is not null &&
5860
// Fallback to the process name
5961
return Path.GetFileNameWithoutExtension(processPath);
6062
}
63+
64+
[Fact]
65+
public unsafe void FunctionWithSharedNativeArrayCanBeCalled()
66+
{
67+
LPPROC_THREAD_ATTRIBUTE_LIST attributeList = default;
68+
nuint size = 0;
69+
try
70+
{
71+
PInvoke.InitializeProcThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST.Null, 1, ref size);
72+
73+
attributeList = new LPPROC_THREAD_ATTRIBUTE_LIST((void*)Marshal.AllocHGlobal((int)size));
74+
75+
PInvoke.InitializeProcThreadAttributeList(attributeList, 1, ref size);
76+
77+
short machineType = 0;
78+
Span<byte> buffer = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref machineType, 1));
79+
PInvoke.UpdateProcThreadAttribute(attributeList, 0, PInvoke.PROC_THREAD_ATTRIBUTE_MACHINE_TYPE, buffer, null, null);
80+
}
81+
finally
82+
{
83+
if (!attributeList.IsNull)
84+
{
85+
PInvoke.DeleteProcThreadAttributeList(attributeList);
86+
87+
Marshal.FreeHGlobal((IntPtr)attributeList.Value);
88+
}
89+
}
90+
}
6191
}

0 commit comments

Comments
 (0)