Skip to content
Merged
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 @@ -97,16 +97,19 @@ protected override void InitializeWorker(CompilationStartAnalysisContext context
var boolType = context.Compilation.GetSpecialType(SpecialType.System_Boolean);
var guidType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemGuid);

var builder = ImmutableHashSet.CreateBuilder<INamedTypeSymbol>();
builder.AddIfNotNull(charType);
builder.AddIfNotNull(boolType);
builder.AddIfNotNull(stringType);
builder.AddIfNotNull(guidType);
var invariantToStringTypes = builder.ToImmutableHashSet();
var nullableT = context.Compilation.GetSpecialType(SpecialType.System_Nullable_T);
var invariantToStringMethodsBuilder = ImmutableHashSet.CreateBuilder<IMethodSymbol>();
AddValidToStringMethods(invariantToStringMethodsBuilder, nullableT, charType);
AddValidToStringMethods(invariantToStringMethodsBuilder, nullableT, boolType);
AddValidToStringMethods(invariantToStringMethodsBuilder, nullableT, stringType);
AddValidToStringMethods(invariantToStringMethodsBuilder, nullableT, guidType);
var invariantToStringMethods = invariantToStringMethodsBuilder.ToImmutable();

var dateTimeType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDateTime);
var dateTimeOffsetType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDateTimeOffset);
var timeSpanType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTimeSpan);
var dateTimeToStringFormatMethod = GetToStringWithFormatStringParameter(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDateTime));

var dateTimeOffsetToStringFormatMethod = GetToStringWithFormatStringParameter(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDateTimeOffset));

var timeSpanToStringFormatMethod = GetToStringWithFormatStringParameter(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTimeSpan));

var stringFormatMembers = stringType.GetMembers("Format").OfType<IMethodSymbol>();

Expand Down Expand Up @@ -145,6 +148,9 @@ protected override void InitializeWorker(CompilationStartAnalysisContext context
var installedUICulturePropertyOfComputerInfoType = computerInfoType?.GetMembers("InstalledUICulture").OfType<IPropertySymbol>().FirstOrDefault();

var obsoleteAttributeType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemObsoleteAttribute);

var guidParseMethods = guidType?.GetMembers("Parse") ?? ImmutableArray<ISymbol>.Empty;

#endregion

context.RegisterOperationAction(oaContext =>
Expand All @@ -157,8 +163,8 @@ protected override void InitializeWorker(CompilationStartAnalysisContext context
targetMethod.ContainingType.IsErrorType() ||
(activatorType != null && activatorType.Equals(targetMethod.ContainingType)) ||
(resourceManagerType != null && resourceManagerType.Equals(targetMethod.ContainingType)) ||
IsValidToStringCall(invocationExpression, invariantToStringTypes, dateTimeType, dateTimeOffsetType, timeSpanType) ||
IsValidParseCall(invocationExpression, guidType))
IsValidToStringCall(invocationExpression, invariantToStringMethods, dateTimeToStringFormatMethod, dateTimeOffsetToStringFormatMethod, timeSpanToStringFormatMethod) ||
IsValidParseCall(invocationExpression, guidParseMethods))
{
return;
}
Expand Down Expand Up @@ -280,6 +286,25 @@ protected override void InitializeWorker(CompilationStartAnalysisContext context
}, OperationKind.Invocation);
}

private static IMethodSymbol? GetToStringWithFormatStringParameter(INamedTypeSymbol? type)
{
return type?.GetMembers("ToString").OfType<IMethodSymbol>().FirstOrDefault(s => s.Parameters is [{ Type.SpecialType: SpecialType.System_String }]);
}

private static void AddValidToStringMethods(ImmutableHashSet<IMethodSymbol>.Builder validToStringMethodsBuilder, INamedTypeSymbol nullableT, INamedTypeSymbol? type)
{
if (type is null)
{
return;
}

validToStringMethodsBuilder.AddRange(GetToStringMethods(type));
validToStringMethodsBuilder.AddRange(GetToStringMethods(nullableT.Construct(type)));

static IEnumerable<IMethodSymbol> GetToStringMethods(INamedTypeSymbol namedTypeSymbol)
=> namedTypeSymbol.GetMembers("ToString").OfType<IMethodSymbol>().WhereNotNull();
}

private static IEnumerable<int> GetIndexesOfParameterType(IMethodSymbol targetMethod, INamedTypeSymbol formatProviderType)
{
return targetMethod.Parameters
Expand All @@ -293,17 +318,11 @@ private static ParameterInfo GetParameterInfo(INamedTypeSymbol type, bool isArra
return ParameterInfo.GetParameterInfo(type, isArray, arrayRank, isParams);
}

private static bool IsValidToStringCall(IInvocationOperation invocationOperation, ImmutableHashSet<INamedTypeSymbol> invariantToStringTypes,
INamedTypeSymbol? dateTimeType, INamedTypeSymbol? dateTimeOffsetType, INamedTypeSymbol? timeSpanType)
private static bool IsValidToStringCall(IInvocationOperation invocationOperation, ImmutableHashSet<IMethodSymbol> validToStringMethods,
IMethodSymbol? dateTimeToStringFormatMethod, IMethodSymbol? dateTimeOffsetToStringFormatMethod, IMethodSymbol? timeSpanToStringFormatMethod)
{
var targetMethod = invocationOperation.TargetMethod;

if (targetMethod.Name != "ToString")
{
return false;
}

if (invariantToStringTypes.Contains(UnwrapNullableValueTypes(targetMethod.ContainingType)))
if (validToStringMethods.Contains(targetMethod))
{
return true;
}
Expand All @@ -316,44 +335,20 @@ private static bool IsValidToStringCall(IInvocationOperation invocationOperation
}

// Handle invariant format specifiers, see https://github.com/dotnet/roslyn-analyzers/issues/3507
if ((dateTimeType != null && targetMethod.ContainingType.Equals(dateTimeType)) ||
(dateTimeOffsetType != null && targetMethod.ContainingType.Equals(dateTimeOffsetType)))
if (targetMethod.Equals(dateTimeToStringFormatMethod, SymbolEqualityComparer.Default) || targetMethod.Equals(dateTimeOffsetToStringFormatMethod, SymbolEqualityComparer.Default))
{
return s_dateInvariantFormats.Contains(format);
}

if (timeSpanType != null && targetMethod.ContainingType.Equals(timeSpanType))
if (targetMethod.Equals(timeSpanToStringFormatMethod, SymbolEqualityComparer.Default))
{
return format == "c";
}

return false;

// Local functions

static INamedTypeSymbol UnwrapNullableValueTypes(INamedTypeSymbol typeSymbol)
{
if (typeSymbol.IsNullableValueType() && typeSymbol.TypeArguments[0] is INamedTypeSymbol nullableTypeArgument)
return nullableTypeArgument;
return typeSymbol;
}
}

private static bool IsValidParseCall(IInvocationOperation invocationOperation, INamedTypeSymbol? guidType)
{
var targetMethod = invocationOperation.TargetMethod;

if (targetMethod.Name != "Parse")
{
return false;
}

if (targetMethod.ContainingType.Equals(guidType))
{
return true;
}

return false;
}
private static bool IsValidParseCall(IInvocationOperation invocationOperation, ImmutableArray<ISymbol> guidParseMethods)
=> guidParseMethods.Contains(invocationOperation.TargetMethod);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -952,25 +952,8 @@ public async Task CA1305_GuidParse_NoDiagnosticsAsync()
{
await new VerifyCS.Test
{
ReferenceAssemblies = new ReferenceAssemblies(""), // workaround for lack of .NET 7 Preview 4 reference assemblies
ReferenceAssemblies = ReferenceAssemblies.Net.Net70,
TestCode = @"
namespace System
{
public class Object { }
public abstract class ValueType { }
public struct Void { }
public class String { }
public interface IFormatProvider { }
public struct Guid
{
public static Guid Parse(string s) => default;
public static Guid Parse(string s, IFormatProvider provider) => default;
}
}
namespace System.Globalization
{
public class CultureInfo : IFormatProvider { }
}
namespace Test
{
using System;
Expand Down