Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suspicious Cast from Char to Int32 Analyzer #5506

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

NewellClark
Copy link
Contributor

Fix #47927
I noticed there wasn't a category for Correctness in DiagnosticCategoriesAndIdRanges.txt, so I went ahead and added one. However, when I did, I found that there are a couple analyzers that use this category, even though it wasn't present in the file, so I had to add an RS range of IDs as well.

The analyzer reports a diagnostic on implicit char-to-int conversions on arguments to any StringBuilder constructor, as well as string.Split(char, int, StringSplitOptions) and string.Split(string, int, StringSplitOptions).

The fixer converts string.Split calls to either string.Split(char[], StringSplitOptions) or string.Split(string[], StringSplitOptions), and converts StringBuilder(int) calls to StringBuilder(string) calls. Violations in other StringBuilder constructors are reported but not fixed.

I ran the analyzer against dotnet/runtime and found nothing. I tried to run the analyzer against roslyn-analyzers, but a bunch of other analyzers (not my analyzer) kept throwing exceptions. Maybe I'm running against roslyn-analyzers incorrectly?

- Supports string.Split(char, int, StringSplitOptions)
- Supports string.Split(string, int, StringSplitOptions)
- Supports StringBuilder(int)
- Fix possible null-deref in new IOperationExtensions.GetArgumentsInParameterOrder() method
@NewellClark NewellClark requested a review from a team as a code owner September 19, 2021 14:42
@codecov
Copy link

codecov bot commented Sep 22, 2021

Codecov Report

Merging #5506 (01f38d8) into main (b94f168) will increase coverage by 0.00%.
The diff coverage is n/a.

❗ Current head 01f38d8 differs from pull request most recent head aca7aac. Consider uploading reports for the commit aca7aac to get more accurate results

@@           Coverage Diff           @@
##             main    #5506   +/-   ##
=======================================
  Coverage   95.49%   95.50%           
=======================================
  Files        1217     1217           
  Lines      277298   277298           
  Branches    16741    16741           
=======================================
+ Hits       264817   264829   +12     
+ Misses      10280    10279    -1     
+ Partials     2201     2190   -11     

@jmarolf jmarolf changed the base branch from release/7.0.1xx to main September 28, 2021 22:01
namespace Microsoft.NetCore.CSharp.Analyzers.Runtime
{
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class CSharpSuspiciousCastFromCharToIntFixer : SuspiciousCastFromCharToIntFixer
Copy link
Contributor

@mavasani mavasani Oct 25, 2021

Choose a reason for hiding this comment

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

A more declarative approach would be to have the base type be a generic type with TInvocationExpressionSyntax as the type parameter and the below method GetMemberAccessExpressionSyntax take a TInvocationExpressionSyntax parameter.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at the second method, probably the type should be SuspiciousCastFromCharToIntFixer<TInvocationExpressionSyntax, TParameterSyntax>

<value>Implicit cast from char to int in method call is suspicious. Consider using a different overload.</value>
</data>
<data name="SuspiciousCastFromCharToIntMessage" xml:space="preserve">
<value>Suspicious implicit cast from char to int</value>
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure if this message is descriptive enough for the users. Maybe give more context in the message that this argument is being used for count parameter and the char is converted into int and used as a count, rather than a string split character. We may also want to recommend users that they probably intended to use a different overload that takes multiple string split characters.


if (stringSplitMethods.Contains(invocation.TargetMethod))
{
foreach (var argument in invocation.Arguments)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we instead be more specific and check only the argument specified for the second parameter? You probably want to invoke the below method with parameterIndex = 1

public static IArgumentOperation GetArgumentForParameterAtIndex(
this ImmutableArray<IArgumentOperation> arguments,
int parameterIndex)

void AddIfNotNull(IMethodSymbol? method)
{
if (method is not null)
builder!.Add(method);
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, is builder a nullable reference type? Why is ! required here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was complaining because AddIfNotNull is a local function and builder is a closed-over local. We know that AddIfNotNull is never called before builder is initialized, but for some reason the compiler does not know that and was reporting a warning.

if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringSplitOptions, out var stringSplitOptionsType))
return ImmutableHashSet<IMethodSymbol>.Empty;

var builder = ImmutableHashSet.CreateBuilder<IMethodSymbol>(SymbolEqualityComparer.Default);
Copy link
Contributor

Choose a reason for hiding this comment

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

We also have PooledHashSet in the repository that can likely be used.

{
return argument.Value is IConversionOperation conversion &&
conversion.IsImplicit &&
conversion.Operand.Type.SpecialType is SpecialType.System_Char &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Can the operand or its type be null for error cases or say if null literal is used?

return argument.Value is IConversionOperation conversion &&
conversion.IsImplicit &&
conversion.Operand.Type.SpecialType is SpecialType.System_Char &&
argument.Parameter.Type.SpecialType is SpecialType.System_Int32;
Copy link
Contributor

Choose a reason for hiding this comment

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

This check can be avoided if we invoked IsImplicitCharToIntConversion only for the argument corresponding to the parameter that we knew is of int type.


foreach (var argument in creation.Arguments)
{
if (IsImplicitCharToIntConversion(argument))
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment here. I think we should not do this check for all arguments, but instead narrow it down to specific constructor parameter/argument where we know the parameter is an integral count.

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
Copy link
Contributor

Choose a reason for hiding this comment

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

Revert?

[InlineData(
"'c', ({|#0:'a'|}), System.StringSplitOptions.None",
"new char[] { 'c', 'a' }, System.StringSplitOptions.None")]
public Task StringSplit_CharInt32StringSplitOptions_Fixed_CS(string testArgumentList, string fixedArgumentList)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add tests with named arguments, both is parameter order and in non-parameter order?

Copy link
Contributor

Choose a reason for hiding this comment

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

Please also add tests where the some parameters have named arguments and some don't, for example, if the split options parameter had a named argument, we should retain the argument name in fixed code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I see you do have some tests with named arguments just above. I am not sure if we want to change the argument order in the fixed code though. For example if options: System.StringSplitOptions.None is the first argument, we should probably retain it so in the fixed code and have the code fix add a named argument for new array argument. If it's too much work to get it working in the fixer, then its fine to retain what you have.

{
private StringBuilderCtor_Int32(IMethodSymbol ctor) : base(ctor) { }

public static StringBuilderCtor_Int32? Create(Compilation compilation, RequiredSymbols symbols)
Copy link
Contributor

Choose a reason for hiding this comment

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

TryCreate?

Copy link
Contributor

@mavasani mavasani left a comment

Choose a reason for hiding this comment

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

Overall looks pretty good to me. Have few minor suggestions, I can review again once you have addressed/resolved them. Thanks!

@mavasani
Copy link
Contributor

Ping @NewellClark

@stephentoub
Copy link
Member

@NewellClark, are you still working on this?

@NewellClark
Copy link
Contributor Author

@stephentoub My apologies. I completely forgot I was assigned to that. I will resume work on it immediately.

@stephentoub
Copy link
Member

No need to apologize. Thanks for working on these.

@buyaa-n
Copy link
Contributor

buyaa-n commented May 8, 2023

Ping @NewellClark are you still planning to work on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add analyzer: Prevent wrong usage of string Split
5 participants