Skip to content

string, params object[] parameter sanity checking #97300

Open
@jonpryor

Description

@jonpryor

(From the "Save me from myself" department…)

Describe the problem you are trying to solve

C# params arrays are awesome:

partial class Utilities {
    public static void LogError(UsefulInfo info, string format, params object[] args)
    {
        string message = string.Format(format, args);
        // Do something with `message`…
    }
}

They're also a footgun, lying in wait:

class App {
    static UsefulInfo info =public static void Whatever()
    {
        try {
            DoSomething();
        }
        catch (Exception e) {
            Utilities.LogError(info, e.ToString());
        }
    }
}

Quick, what's wrong with the above code? It would compile (given enough extra code), and it certainly looks reasonable, and similar code constructs have passed code review innumerable times.

What Could Possibly Go Wrong?

static void DoSomething()
{
    throw new Exception("{tag-name} lol");
}

What happens is the above sketch eventually effectively calls:

string.Format("{tag-name} lol");
// -or-
string.Format("{tag-name} lol", new object[0]);

which promptly throws an exception:

System.FormatException: Input string was not in a correct format.
  at System.Text.StringBuilder.AppendFormatHelper (System.IFormatProvider provider, System.String format, System.ParamsArray args)
  at System.String.FormatHelper (System.IFormatProvider provider, System.String format, System.ParamsArray args)
  at System.String.Format (System.String format, System.Object[] args)

There are (at least) two mitigations for this: overloading, and using the format string.

Overloading: Method overloading nicely "solves"/"avoids" the problem:

partial class Utilities {
    public static void LogError(UsefulInfo info, string message) =>// doesn't call string.Format()
}

The callsite of Utilities.LogError(info, e.ToString()) is unchanged, but now avoids calling string.Format(), and thus avoids the FormatException.

Explicit format strings: occasionally method overloading isn't (easily) possible, in which case the format string should be explicitly specified:

Utilities.LogError(info, "{0}", e.ToString());

Describe suggestions on how to achieve the rule

When an analyzer rule encounters a method invocation:

  1. Does the resolved method to-be-called end in a string, params *whatever*[] parameter "pair"? If it does, continue to (2).
  2. Are there any values in the params *whatever*[] parameter? If there are, then it's "fine". (It might not be fine; see also CA2241 - Possibility to support custom string formatting methods? roslyn-analyzers#2799! But it's not what this issue is trying to look for.). If there are no values in the params object[] parameter, continue to (3).
  3. Issue a warning that the method invocation is potentially problematic.

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.Runtimecode-analyzerMarks an issue that suggests a Roslyn analyzerneeds-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions