Skip to content

Conversation

@Evangelink
Copy link
Member

Fix #1254

@Evangelink Evangelink requested a review from a team as a code owner September 25, 2020 17:21
if (missingIndexes.Any())
{
// can't deal with this for now.
context.ReportDiagnostic(invocation.CreateDiagnostic(Rule));
Copy link
Member Author

Choose a reason for hiding this comment

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

We may want to report with a different diagnostic (message + desc).

Copy link

@jmarolf jmarolf Oct 5, 2020

Choose a reason for hiding this comment

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

I think, since we are saying we want to break with legacy FxCop behavior that introducing a new, more specific warning message would be nice.

Copy link
Member Author

Choose a reason for hiding this comment

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

I would still keep the same rule ID except if you think that's valuable to let users disable some part of the rule (I don't see the need as I don't expect much FPs).

{|#1:" + invocation + @"(""{0} {1} {2}"", 1)|};
{|#2:" + invocation + @"(""{0} {1} {2}"", 1, 2)|};
// These cases are not handled as we cannot find a parameter named format
Copy link
Member Author

Choose a reason for hiding this comment

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

I was tempted to update the code to handle these cases but I was a bit worried about the perf impact of analyzing the extra overloads. Let me know what you think.

Copy link

Choose a reason for hiding this comment

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

I would go ahead and handle them.

@codecov
Copy link

codecov bot commented Sep 29, 2020

Codecov Report

❌ Patch coverage is 99.15254% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.83%. Comparing base (5f3a4ce) to head (9003d4f).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4226      +/-   ##
==========================================
+ Coverage   95.81%   95.83%   +0.02%     
==========================================
  Files        1170     1170              
  Lines      266447   266951     +504     
  Branches    16050    16051       +1     
==========================================
+ Hits       255293   255836     +543     
+ Misses       9102     9087      -15     
+ Partials     2052     2028      -24     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mavasani
Copy link

mavasani commented Oct 5, 2020

@jmarolf You have the most context on this analyzer. Can you please review?

@Evangelink
Copy link
Member Author

@mavasani @jmarolf I think I need to rewrite the analyzer to use the regex as suggested in the ticket but I am fine with having a first review to start with, it shouldn't change much the main portion of the code.

}

private static int GetFormattingArguments(string format)
private static HashSet<int>? GetStringFormatItemIndexes(string format)
Copy link

Choose a reason for hiding this comment

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

I would extract this to a helper class and add unit tests explicitly for it. If we are going to maintain our own fork of the mscorlib parser code we should have tests to document its behavior.


namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests
{
public class ProvideCorrectArgumentsToFormattingMethodsTests
Copy link

Choose a reason for hiding this comment

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

Can we add a test for __arglist?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you mean an empty arglist? We have some tests with __arglist(5)

Copy link

@jmarolf jmarolf Oct 5, 2020

Choose a reason for hiding this comment

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

yep

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure how to get it to work, I was expecting the following to be good but that's not the case:

public void M(__arglist)
{
    System.Console.WriteLine("", __arglist);
}

Copy link

@jmarolf jmarolf left a comment

Choose a reason for hiding this comment

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

looks good! I would appreciate it if we could add a few more tests while we are here and I think having a separate, more descriptive diagnostic message would be nice

<data name="ProvideCorrectArgumentsToFormattingMethodsMessage" xml:space="preserve">
<value>Provide correct arguments to formatting methods</value>
<data name="ProvideCorrectArgumentsToFormattingMethodsMessageArgs" xml:space="preserve">
<value>Incorrect number of arguments passed to this formatting method call</value>
Copy link
Member Author

Choose a reason for hiding this comment

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

Do we want to add how many params are expected and how many were passed?

Copy link

Choose a reason for hiding this comment

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

This sounds like a great idea

<value>Replace with 'string.Contains'</value>
</data>
<data name="ProvideCorrectArgumentsToFormattingMethodsMessageFormatItem" xml:space="preserve">
<value>Some string format items are missing from this formatting method call</value>
Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know if it brings much to add the missing indexes, WDYT?

Copy link

Choose a reason for hiding this comment

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

Considering we will "squiggle" the entire thing I think some text explaining how to fix what is wrong would help. Saying

"Some string format items are missing from this formatting method call. Nothing was passed for index '0'"

isDataflowRule: false);

internal static DiagnosticDescriptor MissingFormatItemRule = DiagnosticDescriptorHelper.Create(
RuleId,
Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think there is a need for a different ID (i.e. being able to activate one behavior without the other).

Copy link

Choose a reason for hiding this comment

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

I think that users may want to configure different severities for these rules which would necessitate a different ID.

  • having too few parameters, run time error, should be a warning
  • having too many parameters, not a runtime error, should probably be info by default

/// <summary>
/// This regex is used to remove escaped brackets from the format string before looking for valid {} pairs.
/// </summary>
private static readonly Regex s_removeEscapedBracketsRegex = new("{{");
Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure if it's better to use the compiled regex here. It's not present in IDE0043 so I am assuming this was a conscious call.

Copy link

Choose a reason for hiding this comment

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

compiling the regex has binary size implications in visual studio which we are very paranoid about. Having the regex be precompiled here is fine.

// but we want to skip it only in C# since VB doesn't support __arglist
if (info.ExpectedStringFormatArgumentCount >= 0 &&
invocation.TargetMethod.IsVararg &&
invocation.Language == LanguageNames.CSharp)
Copy link
Member Author

Choose a reason for hiding this comment

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

This filter was suggested but not present in the original implementation. Let me know if you prefer to revert this.

Copy link

Choose a reason for hiding this comment

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

I am fine with this change

if (missingIndexes.Any())
{
// can't deal with this for now.
context.ReportDiagnostic(invocation.CreateDiagnostic(MissingFormatItemRule));
Copy link
Member Author

Choose a reason for hiding this comment

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

Do we want to report only on the string parameter?

@Evangelink
Copy link
Member Author

There is an extra user option available in IDE0043, do we want to integrate it?

@jmarolf
Copy link

jmarolf commented Oct 7, 2020

There is an extra user option available in IDE0043, do we want to integrate it?

If you have the time, I would love that. I think each of these options should be a different diagnostic Id that the user can configure independently.

@jmarolf
Copy link

jmarolf commented Oct 7, 2020

@mavasani I would like providing too few parameters (which will cause a runtime error) to be enabled as a warning for analysis level 6. Do you have any objections?

@mavasani
Copy link

mavasani commented Oct 8, 2020

@mavasani I would like providing too few parameters (which will cause a runtime error) to be enabled as a warning for analysis level 6. Do you have any objections?

Sounds reasonable. However, lets follow a process for promoting any CA rule to a warning:

  1. Create a separate design issue for the proposal
  2. Discuss it via a design meeting with NetAnalyzers v-team OR get confirmation via email
  3. Run the analyzer over dotnet/roslyn and dotnet/runtime to get confidence on no false positives and acceptable performance
  4. Open a PR to promote it as a warning
  5. File a doc issue on dotnet/docs to document breaking change in net6.0

@jmarolf
Copy link

jmarolf commented Oct 8, 2020

@mavasani sounds like a plan. Looking over my notes from the last meeting I believe this has already been approved, but I agree let's not put foist those additional requirements on this PR. I'll create a follow up PR after this is merged.

@mavasani
Copy link

mavasani commented Oct 8, 2020

I'll create a follow up PR after this is merged.

As part of that we should also bump the analyzer to warning in dotnet/roslyn and dotnet/runtime and confirm no false positives and acceptable performance. I think that should be our miniumn quality gate before bumping up a rule to a warning.

@Evangelink
Copy link
Member Author

I am working on the various open comment but I have a side question regarding the descriptors we want to create, basically we have the following use-cases:

  1. Not enough args compared for well-known string format methods (overload with expected arguments)
    a. Console.WriteLine("{0} {1}", 42); -> causes runtime failure = Descriptor1 (e.g. NotEnoughArgsRule)
    b. Console.WriteLine("{1}", 42); -> causes runtime failure = Descriptor1 (e.g. NotEnoughArgsRule) but maybe also Descriptor2 (e.g. NotEnoughArgsMissingFormatItemRule). In this case we might want Descriptor2 to have the same ID as 1 but with a different message. I wouldn't report 2 diag on this line because we don't know what's the correct fix (i.e. it could be change 1 to 0 or add one more parameter).

  2. Not enough args compared for well-known string format methods (overload without expected arguments). For example, Console.WriteLine("{0} {1}") or Console.WriteLine("{1}"), there is no runtime error so we probably want to use Descriptor3 (e.g. FormatItemInNonStringFormatMethodRule) and maybe Descriptor4 (e.g. FormatItemInNonStringFormatMethodMissingFormatItemRule) for the case with the missing {0}.

  3. Correct number of args but missing format item (e.g. Console.WriteLine("{1}", 1, 2)) -> Descriptor5 (e.g. EnoughArgsMissingFormatItemRule).

  4. Too many args for well-known string format methods. For example, Console.WriteLine("{0}", 1, 2) -> Descriptor6 (e.g. TooManyArgsRule) and for Console.WriteLine("{1}", 1, 2, 3) Descriptor7 (e.g. TooManyArgsMissingFormatItemRule).

In addition to the previous cases, there is a user option to provide additional string format methods, shall we reuse the previous descriptors or create a new set of them? Especially for case 1, where we are not sure if these additional methods will cause the runtime error so we might not want them to be raised as Warning by default.

Regarding the user-option available in roslyn - IDE0043 looking at the tests it seems that this is used to validate what I have called missing format item here. Do we want to keep this as a separate option?

@Evangelink
Copy link
Member Author

I am wondering what can be done in term of diagnostic ID, I mean could we depreciate an old ID? I'd like to avoid having a too big gap between the 2 analyzers ID. The next available ID is CA2250. Except if we want to have 2 separate categories too.

I am also wondering if we want to simply bail-out for C# varargs or if we still want to report for missing format indexes? Related to this subject, I would need help for the syntax of the empty __arglist, I cannot make it compile.

I will push a WIP soon, here are the remaining steps:

  1. Change location of the diagnostics
  2. Update all title, message and descriptions
  3. Add support for detection of format items in overload without the format string argument
  4. Ensure I have ported all the editorconfig from CA2241 and IDE0043

@mavasani
Copy link

@Evangelink Let's not deprecate CA2241 ID, let's just repurpose it for one of the new buckets. Microsoft.CodeAnalysis.FxCopAnalyzers is soon going to be deprecated in favor of Microsoft.CodeAnalysis.NetAnalyzers, and we do not need to preserve any backcompat behavior from FxCop in the latter, so I wouldn't worry about breaking changes in CA2241.

@Evangelink
Copy link
Member Author

Evangelink commented Oct 20, 2020

It just feels a little weird as a user to have two really related rules so separated while I don't see much people who wouldn't both running.

@mavasani
Copy link

It just feels a little weird as a user to have two really related rules so separated while I don't see much people who wouldn't both running.

We already have multiple such cases, so it should be fine. Rule IDs should essentially be considered opaque.

csharpTest.ExpectedDiagnostics.Add(
// Test0.cs(8,17): warning CA2241: Provide correct arguments to formatting methods
GetCSharpResultAt(8, 17));
// TODO: Make sure to report only the right diagnostic not both
Copy link
Member Author

Choose a reason for hiding this comment

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

@jmarolf @mavasani Do we have some kind of built-in mechanism I can rely on to fix this? Otherwise I think I will need to return the list of diagnostic allowed and use this at the time where I report.

Copy link

@jmarolf jmarolf Nov 24, 2020

Choose a reason for hiding this comment

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

@sharwell as he is the expert on test framework helpers. I am not aware of one.

Comment on lines +858 to +861
// Not enough args
var s3 = MyFormat(""{0} {1}"", 1);
// Not enough args and missing format index
var s4 = MyFormat(""{0} {2}"", 1);
Copy link
Member Author

Choose a reason for hiding this comment

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

I am tempted to have a separate set of descriptors for these cases (they will use CA2241 as the too many cases) because we are not sure these will lead to runtime failure as this is a custom implementation.

Copy link

@jmarolf jmarolf Nov 24, 2020

Choose a reason for hiding this comment

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

I would be fine with not issuing these diagnostics at all to start. Yes, historically we've done this but and I can see the utility but I would not turn these on for folks by default. This is always going to be something that you need to opt into. I like the idea of having a separate ID for custom methods so folks can still get these warnings on their custom loggers.

Comment on lines +968 to +971
// Not enough args
var s3 = MyFormat(""{0} {1}"", 1);
// Not enough args and missing format index
var s4 = MyFormat(""{0} {2}"", 1);
Copy link
Member Author

Choose a reason for hiding this comment

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

Same comment regarding the separate descriptors.

Copy link

@jmarolf jmarolf Nov 24, 2020

Choose a reason for hiding this comment

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

yep, lets add a new descriptor for this case.

var g = String.Format(p, ""{0}"", 1, 2);
var h = String.Format(p, ""{0} {1}"", 1, 2, 3);
var i = String.Format(p, ""{0} {1} {2}"", 1, 2, 3, 4);
{|#0:" + invocation + @"("""", 1)|};
Copy link
Member Author

Choose a reason for hiding this comment

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

@jmarolf so on these cases I need to report on all extra args, is it right?

Copy link

@jmarolf jmarolf Nov 24, 2020

Choose a reason for hiding this comment

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

I think thats what we want. Once we get the preview out for .NET 6 well see if feedback directs us to change this.

Console.Write(""{0} {1}"", 1, 2, 3);
Console.Write(""{0} {1} {2}"", 1, 2, 3, 4);
Console.Write(""{0} {1} {2} {3}"", 1, 2, 3, 4, 5);
{|#0:" + invocation + @"(""{1}"", 1, 2, 3)|};
Copy link
Member Author

Choose a reason for hiding this comment

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

@jmarolf here I need to report on all extra args + somewhere for the missing format indexes (any suggestion?)

Copy link

@jmarolf jmarolf Nov 24, 2020

Choose a reason for hiding this comment

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

for this case I think the most useful location would be 1, and 3 indicating that these are unused.

var s = String.Format(""{0} {1} {2} {3}"", new object[] {1, 2});
Console.Write(""{0} {1} {2} {3}"", new object[] {1, 2, 3, 4, 5});
Console.WriteLine(""{0} {1} {2} {3}"", new object[] {1, 2, 3, 4, 5});
{|#0:" + invocation + @"(""{0} {1}"", 1)|};
Copy link
Member Author

Choose a reason for hiding this comment

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

Here I highlight all format indexes without associated args, right?

Copy link

@jmarolf jmarolf Nov 24, 2020

Choose a reason for hiding this comment

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

That would be my preference

{
Console.Write(""{0} {1} {2} {3} {4}"", 1, 2, 3, 4, __arglist(5));
Console.WriteLine(""{0} {1} {2} {3} {4}"", 1, 2, 3, 4, __arglist(5));
{|#0:" + invocation + @"(""{1}"", 1)|};
Copy link
Member Author

Choose a reason for hiding this comment

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

How to highlight the missing indexes?

Copy link

@jmarolf jmarolf Nov 24, 2020

Choose a reason for hiding this comment

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

In this case I think we want to have a diagnostic on , 1 and say that is will not be used.

@mavasani
Copy link

@jmarolf Would be you able to review the latest commit and answer @Evangelink's questions?

@jmarolf
Copy link

jmarolf commented Nov 23, 2020

@mavasani I'll review this after lunch, thanks for the ping

@jmarolf
Copy link

jmarolf commented Nov 24, 2020

@Evangelink done with review pass, please ping me if I missed something.

@Evangelink
Copy link
Member Author

@jmarolf Thank you for the various feedback! I will try to work on the pending changes by the end of the week (I might need a bit of time to get back to what's done and what's left to do, it's no longer really fresh in my mind).

Copy link

@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.

@jmarolf
Copy link

jmarolf commented Feb 4, 2021

@Evangelink gentle ping, do you have time to retarget this?

@Evangelink
Copy link
Member Author

@jmarolf I will try to finish this PR by the end of next week. Looking back at it, there is still a lot of work to do.

Base automatically changed from master to main March 5, 2021 02:20
@mavasani
Copy link

@Evangelink Would you have time to proceed further on this PR? Thanks!

@jmarolf jmarolf removed their assignment Jun 6, 2022
DiagnosticCategory.Usage,
RuleLevel.BuildWarningCandidate,
description: s_localizableDescription,
isPortedFxCopRule: true,
Copy link
Member

Choose a reason for hiding this comment

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

Were all rules supported by FxCop?

@Youssef1313
Copy link
Member

Ping @Evangelink

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.

CA2241 Does not fire on skipped placeholder sequence ({0} {1} {3}).

5 participants