-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Static analysis for .NET 5 #30740
Comments
Tagging @mikadumont, who is driving the effort to add the FxCop analyzers package by default to all the new C#/VB netstandard and netcore proejcts created in Visual Studio. Having the above analyzers light by default for new projects would be awesome! |
The string.Concat optimization looks very close to an analyzer I suggested for System.Text.StringBuilder.Append. Maybe we can piggy back on the implementation PR to get a similar set of analyzers done quicker? |
Similarly, there's another suggestion that could be done in System.Collections.Concurrent: https://github.com/dotnet/roslyn-analyzers/issues/2274 |
Is it something community developers could help with? I'd like to be able to help with the development of some (of those) analyzers. |
I have some of these implemented. Should I contribute? |
Assuming you can't find an existing one, I'd start by creating an issue for each analyzer you'd want to add, over on dotnet/roslyn-analyzers. |
I have rarely used IsEmpty because the overwhelming scenario is IsNullOrEmpty, which I always have to create in an extension method. .Net should consider an out of the box solution, similar to String |
... and no to x?.IsEmpty != true |
|
Hi everyone, I'll be co-organizing a small in-company hackathon next weekend and we'd like to pick up some of these (most likely the ArgumentException, PLINQ nops, and maybe the read-only spinlock fields). I'll start by creating new issues in the dotnet/roslyn-analyzers repo to get the discussion started. Anything else I'll need to do to get the ball rolling? |
Giving some feedback:
Another example is the category of performance rules. In LOB apps, 99% of code is cold. Performance does not matter to a large extent. This makes all performance-related rules noise. In my experience, it is hard to find "linter" rules that help productivity in LOB apps. False positives are not just caused by inappropriate rule firing, they also are caused by correct rule firing in places that nobody cares about. The difference between a good and a bad codebase is not the ruleset implemented by C# warnings, FxCop or proposed by this issue. If a codebase is bad, the issues usually are fundamental. Such issues cannot be expressed in terms of rules. This is not to say that this effort is without value. Rather, I want to point out how varied the development scenarios are. This needs to be kept in mind when designing rule sets. From personal experience, I know that developers get tired of warning systems very quickly if the warnings are not actionable. If we want high actionability (and I think that is critical), this forces a tradeoff: Either the ruleset must be very small, or there must be a very good management system for unactionable warnings. I wonder how "industrial strength" analysis software does this (such as Coverity; Eric Lippert works there now). |
I agree with this in general, and this should definitely be the default behavior. However, note that we do support configurable options in the FxCop analyzers package, which can allow end users to customize bunch of aspects of the rule execution:
Obviously, these configurability support can come as follow-up additions based on user requests, but would allow lot of flexibility in cases where the default analyzer behavior is not unanimously acceptable. |
@stephentoub Should Contains actually be |
Typo. It was actually meant to be comparing to -1 instead of to 0. Fixed. Thanks.
Yup. Thanks. Updated to mention it as well. |
Editorconfig file is the chosen and recommended analyzer configuration format starting Dev16.3. You can share configurations between projects by creating an editorconfig at the root of your solution folder. However, Roslyn compiler does not support specifying editorconfig files from arbitrary directories. I presume your feature request is similar to https://github.com/MicrosoftDocs/visualstudio-docs/issues/3409#issuecomment-526201934 and hopefully my replies on that issue clarifies the current state. |
Suggestion for consideration: CorrectnessSpan
|
@jaredpar, added under perf. Thanks. |
I do hope you will extend the editorconfig syntax, because right now you
can't differentiate between underscore starting and camel case...
…On Wed, Sep 4, 2019, 19:22 Manish Vasani ***@***.***> wrote:
Having more analyzers is great, but is there a way to easily share configs
through NuGet similar to ESLint or Stylelint configs on NPM? I haven't seen
anything on doing this and it would greatly simplify adding it to all of my
projects.
Editorconfig <https://editorconfig.org/> file is the chosen and
recommended analyzer configuration format starting Dev16.3. You can share
configurations between projects by creating an editorconfig at the root of
your solution folder. However, Roslyn compiler does not support specifying
editorconfig files from arbitrary directories. I presume your feature
request is similar to MicrosoftDocs/visualstudio-docs#3409 (comment)
<https://github.com/MicrosoftDocs/visualstudio-docs/issues/3409#issuecomment-526201934>
and hopefully my replies on that issue clarifies the current state.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<https://github.com/dotnet/corefx/issues/40739?email_source=notifications&email_token=AAB7TMJJ2T4TS6UU43E6NULQH7OCZA5CNFSM4ISX6FL2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD54EVFY#issuecomment-527977111>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAB7TMPCUMLWVEUYVVWTS4LQH7OCZANCNFSM4ISX6FLQ>
.
|
We could also catch bad usage of |
@ericstj, we could do something like that. However, we'll run into the same issue highlighted in: |
That's something we could fix. |
Thoughts on an analyser that would flag up cases like |
StyleCop already has such analyzers, e.g. |
There's an analyzer for that in Roslynator (RCS1123) as well. |
Here's another one: https://github.com/dotnet/corefx/issues/42093
|
An analyzer that warns when PLINQ would choose sequential mode on a query would be useful. |
I'm working on a PR for TaskCompletionSource ctor passing in a TaskContinuationOptions as a parameter. Getting a CA rule for this and should be able to submit PR. |
It would be good to understand the relationship of this proposal with https://github.com/dotnet/platform-compat. The latter is intended to find API that are deprecated or do not work in Core (eg Thread.Abort, although it doesn't seem to flag that one) or do not work in the targeted OS. It's been somewhat neglected. (I know we discussed this offline but it's good to record here.) |
Yes, seems reasonable to update the defaults for the rule, while also making the rule configurable so end users can specify additional method names/signatures which should be validated. Can you file an issue on https://github.com/dotnet/roslyn-analyzers/issues/new? |
Another offender for |
Thanks, @mavasani. I re-opened dotnet/roslyn-analyzers#2581 and added your comments. |
I captured my ideas for an improved CA1307 rule in the linked issue. Thanks all for the pointer. :) |
Idea: static void ProcessData(int n)
{
for (byte i = 0; i < n; i++)
{
Thread.Sleep(1);
}
} Warn if indexer is tested against a variable of a bigger integer so it will be always less and become an infinite loop due to indexer overflow, e.g. call the method above like this: |
@stephentoub Are you accepting more rules for this issue? Another one that is in our guidelines for C# 7.2+ but codifying it would be great, A warning to be emitted when a method or property is called on an |
Absolutely. However, I've been trying to keep it focused on rules specific to framework types rather than on language features. My hope has been that @jaredpar and @mavasani and co would similarly build rules related to language features. The rule you suggest would also, I expect, be super noisy. And it likely wouldn't work will if the type being passed around was from a reference assembly. |
We're meeting with the analyzer team later this week to discuss how we can incorporate language rules into this analyzer package. It will effectively become what we've previously described as warning waves. |
We have scenarios (particularly in the Intrinsics namespace) where certain APIs expect constant values as parameters. For instance, As a strawman for an analyzer that could detect this, we could consider an So, using the SSE2 example mentioned earlier, the annotation would appear as follows. namespace System.Runtime.Intrinsics.X86
{
public abstract class Sse2 : Sse
{
public static Vector128<short> ShiftLeftLogical(Vector128<short> value, [Immediate( /* Min = 0, Max = 15 */)] byte count);
}
} |
I've seen a few cases in the wild where there will be a call to |
Thanks, @GrabYourPitchforks and @ellismg; I added them to the list. |
Another perf one for you. Replace this: Span<T> theSpan = GetSpan();
theSpan.Fill(default(T)); With this: Span<T> theSpan = GetSpan();
theSpan.Clear(); I see a handful of uses of |
Some of JB annotations that I find useful at times:
|
Thanks for everyone who provided feedback! I've split @stephentoub's list into individual issues. In the future, please file issues for any analyzer/fixer suggestions. |
Note this work is actively happening and you can track progress here: https://github.com/dotnet/runtime/projects/46 |
In FxCop's hey day, many rules were written to help validate the correctness and performance of .NET code. .NET has evolved significantly since then, however, and the creation of new rules has not kept up with the times; we've added very few new analyzers to help validate the correctness and performance of code using the wealth of new types, methods, and patterns that have made their way into .NET in the interim.
For .NET 5, we should augment https://github.com/dotnet/roslyn-analyzers/tree/master/src/Microsoft.NetCore.Analyzers with new analyzers to help further validate the correctness and improve the performance of code written for .NET.
Guidelines:
This issue exists to collect and catalog ideas for such rules to be implemented in the .NET 5 timeframe.
Note that the notes below are just starting ideas... cases called out may not be the right ones to check, may be missing additional cases of importance, etc. When we decide to tackle one of these, part of the work item (which should be split off into a separate issue) will be determining the right heuristics to employ (and potentially even deciding that the false positive rate will be too high to include such a rule at all).
code-analyzerMarks an issue that suggests a Roslyn analyzer
Edit: this work is actively happening and you can track progress here: https://github.com/dotnet/runtime/projects/46
Old List
### CorrectnessSystem
HashCode usage. Find GetHashCode implementations doing manual hashcode combination and switch to HashCode.Combine.
ArgumentException arguments. Find places where
nameof(…)
is passed as the first (rather than second) argument toArgumentException
. Also places wherenameof
is used as the argument name in anArgumentException
(or derived type) but where it's not referring to a parameter.Local DateTime math. Find places where DateTimes known to be in local time (e.g. resulting from DateTime.Now) are used in math.
Buffer.BlockCopy. The count argument is in bytes, but it's easy to accidentally do something like
Buffer.BlockCopy(src, 0, dst, 0, src.Length)
, and if src is an array of elements larger than bytes, this is a bug.Structs passed to Object.ReferenceEquals. Calls to
ReferenceEquals
where we can detect a value type is being passed in are invariably wrong, as the value type will be boxed, and regardless of its value,ReferenceEquals
will always returnfalse
.System.Buffers
ArrayPool misuse. The rule should flag cases where a buffer is potentially being returned to the shared pool multiple times, e.g. where a buffer is returned and then still used after the Return; where a method taking an array by ref Returns the array but doesn't null out the ref; where there's a Return call in a try block, where that same instance is also returned in a catch block, and where something after the Return call in the try block could cause an exception that would trigger the same instance to be returned again in the catch; etc.
MemoryManager finalizers. Adding a finalizer to a
MemoryManager<T>
-derived type is likely an indication of a bug, as it suggests a native resource that could have been handed out in aSpan<T>
is getting cleaned up and potentially while it’s still in use by theSpan<T>
.System.Linq
foreach (var item in src.Select(…).Where(…).AsParallel(…))
, is a nop and should either be removed or the AsParallel moved earlier in the query. I’ve even seen developers writeforeach (var item in src.AsParallel())
thinking it parallelizes theforeach
loop, which it doesn’t… it’d be good to warn about such misuse.OfType
would provably result in an empty sequence, because we can see that the input type in the sequence will never be the specified type.System.Runtime.InteropServices
System.Runtime.Intrinsics
[Immediate]
/[ConstantExpected
attribute that could be put on parameters to indicate that arguments should be constants rather than variables. See Static analysis for .NET 5 #30740 (comment) for details.System.Text.Json
System.Threading
SpinLock
is a mutable struct, meant only for advanced scenarios. Accidentally making aSpinLock
fieldreadonly
can result in silent but significant problems, as any mutations to the instance (e.g. Enter, Exit) will be done on a compiler-generated copy and thus be ignored, making the lock an expensive nop. (It might make sense to extend this analyzer to additional mutable struct types where storing them in a readonly field is likely a bug, e.g.GCHandle
.)CancellationToken
should have been passed but wasn’t, e.g. in an async method that takes aCancellationToken
, a method is called that has an overload that takes aCancellationToken
but a shorter overload that doesn’t take aCancellationToken
was used instead. cc: @marklioSystem.Threading.Tasks
new TaskCompletionSource<T>(object state)
. TCS has a ctor that takes aTaskCreationOptions options
and another ctor that takes anobject state
. There’s a similar enum toTaskCreationOptions
that’s only meant to be used withContinueWith
:TaskContinuationsOptions
. It’s easy to accidentally pass aTaskContinuationOptions
to the TCS ctor, in which case it binds to the overload accepting anobject
and isn’t treating as options at all (and, adding insult to “your options aren’t respected” injury, it also boxes).ValueTask
/ValueTask<T>
correctness. The rule should detect cases where there's a strong liklihood aValueTask{<T>}
is being used incorrectly, e.g. where a single instance may be awaited multiple times, where an instance may be awaited and then also returned out of a method, where an instance may have.GetAwaiter().GetResult()
called on it when it's not obviously already completed, when one is stored into a static field or a dictionary or some other publishing mechanism, etc.Performance
System
Span
string.Concat with substrings. The rule should flag instances of a pattern like
str1 + str2.Substring(…) + str3
orstring.Concat(str1, str2.Substring(…), str3)
and instead switch to using the span-basedstring.Concat(str1, str2.AsSpan(…), str3)
.AsSpan instead of Substring. Somewhat more generally, any time
string.Substring
is used as an argument to something where there's an equivalent overload that takes aReadOnlySpan<char>
(e.g.StringBuilder.Append(string)
vsStringBuilder.Append(ReadOnlySpan<char>)
), the case can be flagged to be changed to useAsSpan
instead.Span.SequenceEquals. Identify open-coded comparison loops between two spans/arrays and suggest replacing with SequenceEquals.
static
ReadOnlySpan<byte>
properties. With a pattern likeprivate static readonly byte[] s_array = new byte[] { ... }
where thestatic readonly byte[]
isinternal
/private
, all consumers ofs_array
could instead operate on a span, and the field is initialized to a newbyte[]
of constant values, it can be changed instead tostatic ReadOnlySpan<byte> Data => new byte[] { ... }
, and the C# compiler will optimize the implementation.Replace local allocations with span stackallocs.. Flag places where known small temporary arrays of primitives (e.g. with a small constant length / where the total size of sizeof(T)*length can be determined to be < some threshold) not inside any loop and not passed around could be replaced by span stackallocs.
Calling stackalloc in loop. Calling stackalloc in a loop leads to stackoverflow. This bug is easy to introduce with the above.
String
string.Concat consolidation. Various patterns of string concatenation generate unnecessary intermediate strings, e.g.
string result = s1 + s2; return result + s3;
will create an unnecessary string allocation, as willstring result = s1 + s2; if (condition) result+= s3; return result;
which could be rewritten asstring result = condition ? s1 + s2 + s3 : s1 + s2;
. The rule would find and offer fixes for such patterns.Primitive substring parsing. The rule should flag instances of a pattern like
int.Parse(str.Substring(…))
and instead switch to using the span-basedint.Parse(str.AsSpan(…))
. This would apply to all of the primitive types, and more generally potentially anything that has an overload taking aReadOnlySpan<char>
instead of astring
.String.IndexOf(...) == -1. Calls to
String.IndexOf(...)
where the result is then just compared to -1 can instead be replaced by calls toString.Contains(...)
.StringBuilder
StringBuilder.Append(char vs string). It's common to see calls to
StringBuilder.Append(string)
with a conststring
containing a single character, e.g.","
. These would be slightly cheaper as calls using a constchar
instead.StringBuilder.Append(primitive.ToString()). The primitive should be passed directly instead.
Stream
Stream.ReadByte/WriteByte missing overrides. The rule should flag custom Stream-derived types that don’t override
ReadByte
orWriteByte
.Stream.ReadAsync/WriteAsync missing overrides. The rule should flag custom Stream-derived types that override
BeginRead/EndRead
orBeginWrite/EndWrite
but that don’t overrideReadAsync
orWriteAsync
. And it should flag custom Stream-derived types that override the array-basedReadAsync
orWriteAsync
but that don’t override theMemory
-based overloads of the same name. (Potentially the same should be done for theSpan
-based overloads, but as the array-basedRead
andWrite
methods are abstract and thus must be overridden, it’s harder to say whether those should be or not.)Stream.Read/WriteAsync overload usage. Find places where
await stream.Read/WriteAsync(array, offset, length, …)
are used and recommend they be replaced by calls to the overloads that take{ReadOnly}Memory<byte>
, to benefit from the return type beingValueTask<int>
.Tuples
Tuple<…>
is being used but where aValueTuple<…>
would suffice, ideally with the C# language syntax employed. There are some cases where aTuple<…>
is beneficial, however, so the patterns identified here would be constrained.Nullable
Nullable<T>.HasValue
, it's common to see calls toNullable<T>.Value
; instead of callingValue
, it's less work to callGetValueOrDefault()
, asValue
repeats theHasValue
check. It's possible a future JIT could optimize away the duplicate check, but if nothing else usingGetValueOrDefault()
makes the job of the JIT easier.Other
params array allocation in loops. Find calls to System.* methods inside loops, where those methods take params arrays and those params arrays are being allocated, and hoist the allocation to before the loop if possible.
Lifting arrays of consts to statics. Arrays of consts passed to known methods on types like System.String (e.g.
string.IndexOfAny(new[] { ',', '.' })
) can be lifted out to static readonly fields.Redundant virtual property calls. Virtual properties are rarely inlined by the JIT and result into expensive virtual calls. Detect cases where the call can be hoisted and the return value reused.
constructor parameters should match property names. In general, constructor arguments who initialize should match properties (casing aside). There are also cases where serializers will match properties to constructor parameters in order to construct immutable types. This would address that too.
System.Collections
Dictionary.ContainsKey(key) followed by Dictionary.Remove(key). The pair can be combined into just the
Remove
call.Dictionary.ContainsKey(key) followed by Dictionary.this[key]. The pair can be combined into just a
TryGetValue
call.!Dictionary.ContainsKey(key) followed by Dictionary.Add. The pair can be combined into just a
TryAdd
call.Dictionary.ContainsKey(key), followed by Dictionary.this[key], followed by Dictionary.Remove(key). The trio can be combined into just the
Remove
call, using the overload acceptingout TValue value
.System.Collections.Concurrent
System.Runtime.InteropServices
sizeof
is much more efficient thanMarshal.SizeOf
. In cases where they'll produce the same answer, the former should be preferred (it'll generally require usingunsafe
, though, so this should only fire ifunsafe
is allowed in the project).System.Threading
System.Threading.Tasks
Using
ValueTask<T>
instead ofTask<T>
. Flagging internal/private methods that returnsT
s that won’t be entirely cached (e.g. bool) and where every caller of the method only ever awaits its result directly.Task.Delay in Task.WhenAny. Flag places where a Task.Delay is used as an argument to WhenAny and where that Task.Delay doesn’t take a cancellation token, in which case the Task.Delay is likely leaving a timer running for longer than is necessary.
Task.WhenAll with one argument. There’s no reason to call WhenAll with a single
Task
; just use theTask
.Task.WaitAll with one argument.
Task.Wait
can be used instead.Style
System.Runtime.InteropServices
System.Threading
if (token.IsCancellationRequested) throw new OperationCanceledException();
orif (token.IsCancellationRequested) throw new OperationCanceledException(token);
and replace withtoken.ThrowIfCancellationRequested();
.cc: @jaredpar, @mavasani, @danmosemsft
The text was updated successfully, but these errors were encountered: