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

Apply outstanding FxCop rules #3007

Merged
merged 32 commits into from
Dec 12, 2019
Merged

Apply outstanding FxCop rules #3007

merged 32 commits into from
Dec 12, 2019

Conversation

huoyaoyuan
Copy link
Contributor

Oh...there're some rules I've already cleaned 3 years ago.
FxCop was old and containing some unsuitable rules. But it's now maintained by the official compiler team, and adding many new morden rules, and more official rules are going into them, so I pick it.
I've turned on the outstanding ones, and turned off ones conflicting with our current design.
I intended to add comments in the rule set, but VS designer will always remove them.

Supressing some hard-to-apply instances to very valuable diagnostics.

Comment on lines 200 to +201
throw new ArgumentOutOfRangeException(
$"Can not weld bindable longs with non-overlapping min/max-ranges. The ranges were [{MinValue} - {MaxValue}] and [{other.MinValue} - {other.MaxValue}].", nameof(them));
nameof(them), $"Can not weld bindable longs with non-overlapping min/max-ranges. The ranges were [{MinValue} - {MaxValue}] and [{other.MinValue} - {other.MaxValue}].");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ArgumentOutOfRangeException puts param name at first, but ArgumentException puts it at last. I'm not realizing this.

private static readonly Action<TexturedVertex2D> default_quad_action;

static TextureGLSingle()
{
QuadBatch<TexturedVertex2D> quadBatch = new QuadBatch<TexturedVertex2D>(100, 1000);
default_quad_action = quadBatch.AddAction;
}
private static readonly Action<TexturedVertex2D> default_quad_action = new QuadBatch<TexturedVertex2D>(100, 1000).AddAction;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Static constructor has performance impact on every instance method call of the type, according to FxCop doc.

Copy link
Member

Choose a reason for hiding this comment

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

Got a link for this?

Copy link
Collaborator

@bdach bdach Dec 9, 2019

Choose a reason for hiding this comment

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

Here's one. It has to do with introducing a flag checking whether the static ctor was ever called before to all static methods and instance ctors.

Here's a sharplab snippet showing the difference between an explicit and an implicit static ctor (note the beforefieldinit flag). As stated in the fxcop documentation the implicit static ctor is not guaranteed to finish before any static method call or instance ctor, but is guaranteed to run before all static field accesses.

But then again, it's just a flag check. I really doubt it'll have real and noticeable performance repercussions.

Copy link
Member

Choose a reason for hiding this comment

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

Seems to be a bit over-the-top in terms of optimisation. That said, no harm if the changes don't affect readability, which seems to be the case for the two in this PR.

There are places we use static ctor for more than just initialisation though, so as long as the fxcop rule accounts (and allows) for that, there shouldn't be an issue.

public bool Equals(FontUsage other) => string.Equals(Family, other.Family) && string.Equals(Weight, other.Weight) && Italics == other.Italics && Size.Equals(other.Size) && FixedWidth == other.FixedWidth;
public bool Equals(FontUsage other) => Family == other.Family && Weight == other.Weight && Italics == other.Italics && Size.Equals(other.Size) && FixedWidth == other.FixedWidth;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

== on string uses SequenceEquals. It should be always used unless absolute reason.

Comment on lines -123 to +132
private static readonly Logger logger;

private static readonly HttpClient client;

static WebRequest()
private static readonly HttpClient client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate })
{
client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate });
client.DefaultRequestHeaders.UserAgent.ParseAdd("osu!");

DefaultRequestHeaders =
{
UserAgent = { ProductInfoHeaderValue.Parse("osu!") }
},
// Timeout is controlled manually through cancellation tokens because
// HttpClient does not properly timeout while reading chunked data
client.Timeout = System.Threading.Timeout.InfiniteTimeSpan;
Timeout = System.Threading.Timeout.InfiniteTimeSpan
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Quite tricky here - object initializer syntax allows you to set nested properties without assigning new objects in the first-level property. I learned this just recently.

Comment on lines 66 to 70
private static readonly MethodInfo method_cocoa_to_ns_string = type_cocoa.GetMethod("ToNSString");
private static readonly MethodInfo method_cocoa_get_string_constant = type_cocoa.GetMethod("GetStringConstant");

public static IntPtr AppKitLibrary;
public static IntPtr FoundationLibrary;
public static IntPtr AppKitLibrary = (IntPtr)type_cocoa.GetField("AppKitLibrary").GetValue(null);
public static IntPtr FoundationLibrary = (IntPtr)type_cocoa.GetField("FoundationLibrary").GetValue(null);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Static inline initializations are executed in the file order. The code is actually already depending on this in the previous lines.

@huoyaoyuan
Copy link
Contributor Author

CodeFactor should be aware about the member complexity before this PR 😟

@bdach
Copy link
Collaborator

bdach commented Nov 17, 2019

I sort of disagree with enabling the ones that have to then be explicitly suppressed due to a lack of better alternative.

  • I believe a blanket ban for static initialisers is too harsh. It leads to suppressions in test code, which is kind of unwarranted since initialisation performance in test code should not matter. Yes, everywhere else it's useful - but not in tests.
  • The zero-length array inspection really shouldn't complain on things like int[0][].

Personally I'd be fine with enabling these if those two were plain warnings. But it seems we currently have a very binary approach of error/no error.

Oh, and I think codefactor just re-complains whenever a file with previously-dismissed warnings is touched.

@huoyaoyuan
Copy link
Contributor Author

I sort of disagree with enabling the ones that have to then be explicitly suppressed due to a lack of better alternative.

I'm neutual on this. Just tried it and find it's only complained in very few places.

Yes, everywhere else it's useful - but not in tests.

I just think plain supression is less work than creating a splited rule set for tests. It supports inheriting, so not much work neither.

The zero-length array inspection really shouldn't complain on things like int[0][].

The analyzer seems not aware on what's the element type.

Personally I'd be fine with enabling these if those two were plain warnings.

Big project should have all won't-fixes suppressed, or it will be hard to distinguish meaningful ones from them.

@@ -59,7 +59,7 @@ internal bool Active
}
else
{
timeEndPeriod(period);
_ = timeEndPeriod(period);
Copy link
Contributor

Choose a reason for hiding this comment

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

Disagree with this style.

@@ -405,7 +405,9 @@ internal static class Interpolation<TValue>
{
public static readonly InterpolationFunc<TValue> FUNCTION;

#pragma warning disable CA1810 // Initialize reference type static fields inline
Copy link
Contributor

@smoogipoo smoogipoo Nov 19, 2019

Choose a reason for hiding this comment

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

You can make these into fields. I'd previously done it in the past, though it didn't lead to significant performance differences (generally the case with static ctor pre-checks that the JIT/EE does) so I didn't commit it.

@@ -38,7 +38,9 @@ public void TestEmptyRectangularToJagged()
public void TestEmptyJaggedToRectangular()
{
int[,] result = null;
Assert.DoesNotThrow(() => result = new int[0][].ToRectangular());
#pragma warning disable CA1825 // Avoid zero-length array allocations.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does Array.Empty<int[]> not work?

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 just feels not natural in this test case. The test is dealing with [][] and [,], using < []> doesn't look good.

Copy link
Contributor

@smoogipoo smoogipoo Nov 19, 2019

Choose a reason for hiding this comment

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

Empty<int[]> makes more sense to me than the original case. It's an empty array containing int[]s, whereas the original code doesn't have the same impact.

@@ -26,9 +26,11 @@ public class TestWebRequest
private static readonly string host;
private static readonly IEnumerable<string> protocols;

#pragma warning disable CA1810 // Initialize reference type static fields inline
Copy link
Contributor

Choose a reason for hiding this comment

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

I personally disagree with disallowing static ctors. The only reason is performance differences which are extremely hard to benchmark in the first place. I'm ok with converting them over on a per-case basis where appropriate, but even benchmarkdotnet gives BenchmarkClass.StaticCtor: Default -> The method duration is indistinguishable from the empty method duration.

I would generally benchmarks when making such changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll change the warning to be disabled. The ones able to be written inline should be fine.


static Interpolation()
// Should match the definition of InterpolationFunc and ValueAt
private static readonly Type[] parameters =
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this and not using reflection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reflection will make the code into a complex expression that's too complicated.
The arguments and its reflection target (InterpolationFunc) live in the same file. There shouldn't be risk of out of sync.

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you mean by "complex expression"? From which perspective? Because it'd be just:

private static Type[] parameters = typeof(InterpolationFunc<TValue>)
                                        .GetMethod(nameof(InterpolationFunc<TValue>.Invoke))
                                        ?.GetParameters().Select(p => p.ParameterType).ToArray();

I also don't understand what you mean "shouldn't be risk of out of sync", if we change the arguments of ValueAt then we'll have to knowingly update this array without the compiler warning us. So yes there is a risk of being out of sync, even if it's in the same file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If inline initialization is used, the whole initialization of FUNCTION will be a huge expression. The expression getting the parameters will be inlined.
Reverted the changes that makes it inline, with some small adjust.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is still risk of out of sync between ValueAt declarations and InterpolationFunc. Creating unused delegates (using direct creation instead of reflection) for them can make compiler error then, but may looks too verbose.

@@ -160,4 +160,15 @@ internal static CacheDependencyDelegate CreateActivator(Type type)
}
}
}

/// <summary>
/// The exception that is thrown when intending to cache <see langword="null"/> using <see cref="CachedAttribute"/>.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// The exception that is thrown when intending to cache <see langword="null"/> using <see cref="CachedAttribute"/>.
/// The exception that is thrown when attempting to cache <see langword="null"/> using <see cref="CachedAttribute"/>.

private static readonly Action<TexturedVertex2D> default_quad_action;

static TextureGLSingle()
{
QuadBatch<TexturedVertex2D> quadBatch = new QuadBatch<TexturedVertex2D>(100, 1000);
default_quad_action = quadBatch.AddAction;
}
private static readonly Action<TexturedVertex2D> default_quad_action = new QuadBatch<TexturedVertex2D>(100, 1000).AddAction;
Copy link
Member

Choose a reason for hiding this comment

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

Got a link for this?

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

Successfully merging this pull request may close these issues.

4 participants