Skip to content

Commit

Permalink
Update xUnit1010 to trigger when a negative integral value is passed …
Browse files Browse the repository at this point in the history
…to an unsigned integral parameter
  • Loading branch information
NormanFrieman authored and bradwilson committed Nov 5, 2024
1 parent 51a32ce commit d25b3a4
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,40 @@ public void TestMethod<T>(T[] a) { }

await Verify.VerifyAnalyzer(source);
}

[Theory]
[MemberData(nameof(SignedIntAndUnsignedInt))]
public async Task FromNegativeInteger_ToUnsignedInteger(
string signedType,
string unsignedType)
{
var source = string.Format(/* lang=c#-test */ """
public class TestClass {{
[Xunit.Theory]
[Xunit.InlineData({{|#0:({0})-1|}})]
public void TestMethod({1} value) {{ }}
}}
""", signedType, unsignedType);
var expected = Verify.Diagnostic("xUnit1010").WithLocation(0).WithArguments("value", unsignedType);

await Verify.VerifyAnalyzer(source, expected);
}

[Theory]
[MemberData(nameof(UnsignedIntegralTypes))]
public async Task FromLongMinValue_ToUnsignedInteger(string unsignedType)
{
var source = string.Format(/* lang=c#-test */ """
public class TestClass {{
[Xunit.Theory]
[Xunit.InlineData({{|#0:long.MinValue|}})]
public void TestMethod({0} value) {{ }}
}}
""", unsignedType);
var expected = Verify.Diagnostic("xUnit1010").WithLocation(0).WithArguments("value", unsignedType);

await Verify.VerifyAnalyzer(source, expected);
}
}

public class DateTimeLikeParameter : X1010_IncompatibleValueType
Expand Down Expand Up @@ -1252,6 +1286,18 @@ public class Explicit {

public static IEnumerable<TheoryDataRow<string>> ValueTypedValues =
IntegerValues.Concat(FloatingPointValues).Concat(BoolValues).Append(new("typeof(int)"));

public static IEnumerable<TheoryDataRow<string>> SignedIntegralTypes =
["int", "long", "short", "sbyte"];

public static IEnumerable<TheoryDataRow<string>> UnsignedIntegralTypes =
["uint", "ulong", "ushort", "byte"];

public static readonly MatrixTheoryData<string, string> SignedIntAndUnsignedInt =
new(
SignedIntegralTypes.Select(r => r.Data),
UnsignedIntegralTypes.Select(r => r.Data)
);
}

public class X1011_ExtraValue
Expand Down
34 changes: 31 additions & 3 deletions src/xunit.analyzers/Utility/ConversionChecker.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -7,11 +9,26 @@ namespace Xunit.Analyzers;

static class ConversionChecker
{
static readonly HashSet<SpecialType> SignedIntegralTypes = [
SpecialType.System_SByte,
SpecialType.System_Int16,
SpecialType.System_Int32,
SpecialType.System_Int64,
];

static readonly HashSet<SpecialType> UnsignedIntegralTypes = [
SpecialType.System_Byte,
SpecialType.System_UInt16,
SpecialType.System_UInt32,
SpecialType.System_UInt64,
];

public static bool IsConvertible(
Compilation compilation,
ITypeSymbol source,
ITypeSymbol destination,
XunitContext xunitContext)
XunitContext xunitContext,
object? valueSource = null)
{
Guard.ArgumentNotNull(compilation);
Guard.ArgumentNotNull(source);
Expand All @@ -32,7 +49,7 @@ public static bool IsConvertible(
var conversion = compilation.ClassifyConversion(source, destination);

if (conversion.IsNumeric)
return IsConvertibleNumeric(source, destination);
return IsConvertibleNumeric(source, destination, valueSource);

if (destination.SpecialType == SpecialType.System_DateTime
|| (xunitContext.Core.TheorySupportsConversionFromStringToDateTimeOffsetAndGuid == true && IsDateTimeOffsetOrGuid(destination)))
Expand Down Expand Up @@ -62,8 +79,13 @@ static bool IsConvertibleTypeParameter(

static bool IsConvertibleNumeric(
ITypeSymbol source,
ITypeSymbol destination)
ITypeSymbol destination,
object? valueSource = null)
{
var isIntegral = long.TryParse(valueSource?.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var integralValue);
if (isIntegral && integralValue < 0 && IsSigned(source) && IsUnsigned(destination))
return false;

if (destination.SpecialType == SpecialType.System_Char
&& (source.SpecialType == SpecialType.System_Double || source.SpecialType == SpecialType.System_Single))
{
Expand All @@ -81,4 +103,10 @@ static bool IsDateTimeOffsetOrGuid(ITypeSymbol destination)

return destination.MetadataName == nameof(DateTimeOffset) || destination.MetadataName == nameof(Guid);
}

static bool IsSigned(ITypeSymbol typeSymbol) =>
SignedIntegralTypes.Contains(typeSymbol.SpecialType);

static bool IsUnsigned(ITypeSymbol typeSymbol) =>
UnsignedIntegralTypes.Contains(typeSymbol.SpecialType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,9 @@ paramsElementType is not null
if (value.Type is null)
continue;

var isCompatible = ConversionChecker.IsConvertible(compilation, value.Type, parameter.Type, xunitContext);
var isCompatible = ConversionChecker.IsConvertible(compilation, value.Type, parameter.Type, xunitContext, value.Kind == TypedConstantKind.Primitive ? value.Value : null);
if (!isCompatible && paramsElementType is not null)
isCompatible = ConversionChecker.IsConvertible(compilation, value.Type, paramsElementType, xunitContext);
isCompatible = ConversionChecker.IsConvertible(compilation, value.Type, paramsElementType, xunitContext, value.Kind == TypedConstantKind.Primitive ? value.Value : null);

if (!isCompatible)
{
Expand Down

0 comments on commit d25b3a4

Please sign in to comment.