Move Roslyn.sln to latest analyzer packages, also including the newly…#25179
Move Roslyn.sln to latest analyzer packages, also including the newly…#25179mavasani wants to merge 7 commits intodotnet:masterfrom
Conversation
… writtern prelimiary Dataflow analysis based Dispose rules.
|
@agocke @jaredpar @jinujoseph - what is the latest version of |
| var metadataCompilation = compilation.RemoveAllSyntaxTrees(); | ||
|
|
||
| ImmutableDictionary<AssemblyIdentity, AssemblyIdentity> assemblyReferenceIdentityMap; | ||
| #pragma warning disable CA2000 // Dispose objects before losing scope - dispose ownership transfer to CreatePEAssemblyForAssemblyMetadata |
There was a problem hiding this comment.
📝 The construction of a disposable object occurs in AssemblyMetadata.Create.
💭 I'm not sure this can or should be treated as an ownership transfer. The CreatePEAssemblyForAssemblyMetadata method returns PEAssemblySymbol, which is not a disposable type. I would only expect factory methods to be candidates for ownership transfer if the constructed object is disposable.
📝 In addition, I believe this is a legitimate resource leak.
There was a problem hiding this comment.
I believe this is a legitimate resource leak.
Ah, I assumed CreatePEAssemblyForAssemblyMetadata returns an IDisposable. This is indeed a resource leak. I will file a bug and add it to the suppression.
There was a problem hiding this comment.
I'm not sure this can or should be treated as an ownership transfer. The CreatePEAssemblyForAssemblyMetadata method returns PEAssemblySymbol, which is not a disposable type. I would only expect factory methods to be candidates for ownership transfer if the constructed object is disposable.
FxCop does not treat factory methods OR constructor invocations as leading to an implicit dispose ownership transfer - it special cased very few cases, which we match. This means that we will have lot more false positives when these are actually dispose ownership transfers, but we will not miss out on flagging true violations where such a transfer does not happen (for example, we never assigned passed in disposable argument to a field or pass it further down). I think this is a general principle for dispose rules and it has been confirmed multiple times by customers - false positives is fine, but identifying as many true violations as possible is super valuable. This is the reason that dispose rules are by design most noisy and most valuable at the same time, which is somewhat ironical.
dotnet/roslyn-analyzers#1617 tracks how we can design this better, but I would not like to mask true leaks by assuming anything as a possible dispose ownership transfer.
There was a problem hiding this comment.
but I would not like to mask true leaks by assuming anything as a possible dispose ownership transfer.
We can avoid masking by pairing the transfer heuristics - each case where signatures treat the code as a transfer of ownership, both the caller and callee agree this is the case. This will allow us to detect the majority cases without losing true positives.
There was a problem hiding this comment.
We can avoid masking by pairing the transfer heuristics - each case where signatures treat the code as a transfer of ownership, both the caller and callee agree this is the case
This will not work. I have added the reasoning here so we capture all the arguments in that design issue.
| // | ||
| bool skipping = true; | ||
| foreach (var t in keyword.TrailingTrivia) | ||
| using (var triviaBuilder = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture)) |
There was a problem hiding this comment.
❔ What's our reason for not treating StringWriter as a special case?
There was a problem hiding this comment.
Special casing disposable types as "okay to not dispose" due to an implementation detail about the current Dispose implementation on a type is not good design. There is nothing that prevents the future versions of the library implementing such types to change their implementation to hold onto to more disposable objects and/or change the implementation of Dispose method, hence I believe that all disposable objects should be disposed, even if we think it is a no-op.
| namespace Microsoft.CodeAnalysis.CSharp | ||
| { | ||
| #pragma warning disable RS0010 | ||
| #pragma warning disable CA1200 |
There was a problem hiding this comment.
❕ Please add the comment with the title of the suppressed rule (same for other cases)
| // Project-level suppressions either have no target or are given | ||
| // a specific target and scoped to a namespace, type, member, etc. | ||
|
|
||
| [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "MSBuild has lot of properties returning arrays")] |
There was a problem hiding this comment.
❓ Why is this suppressed here instead of with <NoWarn> in the project file?
📝 I would be interested in seeing the cases where the warning was reported.
There was a problem hiding this comment.
Why is this suppressed here instead of with in the project file
I guess just a preference and ease of application - a suppression code fix offered in IDE only covers adding global suppress message attribute, not a project level no-warn.
I would be interested in seeing the cases where the warning was reported.
See https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs for an example. There are lots of violations in this project.
There was a problem hiding this comment.
One advantage of <NoWarn> is it suppresses even the execution of the rule. I believe the project-level suppression will result in the rule still running followed by getting filtered out later.
There was a problem hiding this comment.
I believe the project-level suppression will result in the rule still running followed by getting filtered out later.
AnalyzerDriver only executes an analyzer if DiagnosticAnalyzer.SupportedDiagnostics returns at least one supported rule that is effectively enabled in the compilation.
There was a problem hiding this comment.
I don't necessarily have a preference in how we suppress warnings but I do prefer it be done consintently. I see a lot of NoWarn, manually suppression, assembly attributes and rulesets. Feel like we need to pick a way and go with it. Otherwise it's nearly impossible to determine what rules are in effect because you have to check so many different places to see if they are turned off.
| public static Guid ReadAssemblyMvidOrEmpty(Stream stream) | ||
| { | ||
| return ReadAssemblyMvidOrEmpty(new BinaryReader(stream)); | ||
| using (var reader = new BinaryReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), leaveOpen: true)) |
There was a problem hiding this comment.
💭 I'm not confident this is an improvement.
There was a problem hiding this comment.
Yes it is - see my previous comment on why I believe all disposable objects should be disposed. Nothing prevents future version of BinaryReader.Dispose to actually clean up some resources. We should honor IDisposable contract.
There was a problem hiding this comment.
In that sense, it is an improvement, but it is certainly not more readable, hence my 💭
There was a problem hiding this comment.
but it is certainly not more readable
I believe honoring an IDisposable contract that can lead to future leaks is more important then readability.
| // this is unfortunate that if we give stream, compiler will just re-copy whole content to | ||
| // native memory again. this is a way to get around the issue by we getting native memory ourselves and then | ||
| // give them pointer to the native memory. also we need to handle lifetime ourselves. | ||
| #pragma warning disable CA2000 // Dispose objects before losing scope - dispose ownership transfer of metadata instance to s_lifetime |
There was a problem hiding this comment.
❗️ Ownership of a disposable object cannot be transferred to a ConditionalWeakTable, so the comment will need to be updated. In addition, this should should be reviewed as a leak because the ability to dispose of this object is lost in the call to GetReference, below.
There was a problem hiding this comment.
Sure, will file a tracking issue.
|
|
||
| // We don't deterministically release the resulting metadata since we don't know | ||
| // when we should. So we leave it up to the GC to collect it and release all the associated resources. | ||
| #pragma warning disable CA2000 // Dispose objects before losing scope - escaped with GetReference call below, which wraps the metadata instance. |
There was a problem hiding this comment.
❗️ This should be reviewed as a leak, not a transfer (metadata.GetReference does not return a disposable type, so the ability to dispose of metadata is lost).
| } | ||
|
|
||
| ((IWorkspaceOptionService)this.Services.GetService<IOptionService>()).OnWorkspaceDisposed(this); | ||
| _serializationLock.Dispose(); |
There was a problem hiding this comment.
📝 Another case of the always-frustrating SemaphoreSlim. I don't like how disposing of this type makes me worry that new race-condition bugs have been introduced.
There was a problem hiding this comment.
I will revert and file a tracking issue.
| } | ||
|
|
||
| // otherwise, just return new solution | ||
| #pragma warning disable CA2000 // Dispose objects before losing scope - dispose ownership transfer to the caller. |
There was a problem hiding this comment.
❓ How is this an ownership transfer? Solution is not disposable.
| private static readonly TimeSpan ConnectRetryIntervalMs = TimeSpan.FromMilliseconds(20); | ||
|
|
||
| private readonly string _name; | ||
| #pragma warning disable CA2213 // Disposable fields should be disposed - _stream wraps _pipe and is disposed in Close. |
There was a problem hiding this comment.
💭 The code would be clearer to machines if _pipe was wrapped in DelegatingStream when passed to _stream, allowing both of these fields to be disposed in ClientDirectStream.Dispose.
I'm not not confident the pattern of overriding Close() and Dispose(bool) is correct, and it certainly isn't clear. It would be preferable to remove the override of Close() and place all of the cleanup logic in Dispose(bool) (which is already called by Close() in the base class).
| var peStream = FileUtilities.OpenFileStream(path); | ||
|
|
||
| // prefetch image, close stream to avoid locking it: | ||
| #pragma warning disable CA2000 // Dispose objects before losing scope - MetadataImageReference has dispose ownership |
There was a problem hiding this comment.
#pragma warning disable [](start = 0, length = 24)
Can we have just one suppression for the entire method instead of two? These #pragmas add a lot of clutter.
There was a problem hiding this comment.
📝 I found the separation tremendously helpful during review. I would not have been able to complete the review with another approach.
There was a problem hiding this comment.
I agree with @sharwell here - suppression for an entire method can cause future leaks in this method to escape. We are working towards reducing the false positives here (see dotnet/roslyn-analyzers#1617), so we will hopefully revert majority of these suppressions.
| public static Guid ReadAssemblyMvidOrEmpty(Stream stream) | ||
| { | ||
| return ReadAssemblyMvidOrEmpty(new BinaryReader(stream)); | ||
| using (var reader = new BinaryReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), leaveOpen: true)) |
There was a problem hiding this comment.
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true) [](start = 57, length = 83)
Encoding.UTF8 would be sufficient. The code doesn't decode any strings.
There was a problem hiding this comment.
Sure, thanks will change to Encoding.UTF8 - I tried to retain existing semantics as I am not an expert in this part of the code base.
|
I think the problem that throws the analyzer off in many cases is the following pattern: The analyzer flags the following, although there is no real leak here: This is the case e.g. with
|
|
|
||
| // ownership of the stream is passed on PEReader: | ||
| #pragma warning disable CA2000 // Dispose objects before losing scope - ModuleMetadata has dispose ownership | ||
| return new ModuleMetadata(new PEReader(peStream, options)); |
There was a problem hiding this comment.
return new ModuleMetadata(new PEReader(peStream, options)); [](start = 12, length = 59)
Why is this tagged? The PEReader disposes the Stream and ModuleMetadata disposes PEReader.
There was a problem hiding this comment.
This needs dotnet/roslyn-analyzers#1617 to precisely determine dispose ownership transfer without heuristics that can mask true leaks.
There was a problem hiding this comment.
Should the comment link to the issue?
There was a problem hiding this comment.
Yes, I plan to add a commit linking all contentious suppressions to tracking issue(s).
|
@mavasani To your question about "which package version can be used", only rule with master is you can't consume anything newer than the 2.6.* line. If you need 2.7 or later, it needs to go into master-vs-deps. |
|
@mavasani Also, there should be 2.7.0-* packages. I don't know what you even got when you selected 2.8.0. That's 15.7, I think, which hasn't shipped, so there's definitely no release package. Similarly, you'll need to wait for 15.6 to ship to get the first 2.7.0 release package. |
|
i believe @mavasani was looking for 2.6.0 the latest Ioperation API that we shipped, so master should have right package. whats missing then ? |
|
@agocke @jaredpar - We are fine with having this PR in any branch, not necessarily master. We would just like to get some dogfooding for latest analyzers. Can you please recommend which is the next best branch to target and the exact version of |
|
I am also slightly confused why this is not possible due to |
|
@mavasani Unless we backport my fix and patch the released version of microsoft.netcore.compilers, there's no package that you can use that will work cross-platform. |
|
@mavasani I also don't see any NuGet packages for the 15.6 release: https://www.nuget.org/packages/Microsoft.Net.Compilers/ |
|
@agocke I am talking about 15.5 release, i.e. Microsoft.NetCore.Compilers package equivalent to https://www.nuget.org/packages/Microsoft.Net.Compilers/2.6.1 |
|
@mavasani That package does not, and will not, exist. Microsoft.NETCore.Compilers was only release-ready for dev15.6. |
Interesting. Then why is this unshipped and unsupported 2.6.X version of Microsoft.NETCore.Compilers package being used on master branch? I believe any such dependency should be removed from master. It seems weird why an unshipped package is blocking us from moving us to an analyzer package based on shipped Microsoft.CodeAnalysis APIs. |
|
@mavasani It was necessary for us to build on CoreCLR, since previous builds did not work properly. Now we can probably download a corresponding release of .NET CLI for our bootstrap. |
| #pragma warning disable CA2000 // Dispose objects before losing scope | ||
| if (!TryGetItemIDsAndRQName(workspace, changedDocumentIDs, symbol, out var visualStudioWorkspace, out hierarchyToItemIDsMap, out var rqname)) | ||
| #pragma warning restore CA2000 // Dispose objects before losing scope | ||
| { |
There was a problem hiding this comment.
Please don't add an trivia between the if and opening {. I'd move it after {
|
📝 I've requested this get added to the design meeting agenda |
… written preliminary Dataflow-analysis based Dispose rules.
Ask Mode template not completed
Customer scenario
What does the customer do to get into this situation, and why do we think this
is common enough to address for this release. (Granted, sometimes this will be
obvious "Open project, VS crashes" but in general, I need to understand how
common a scenario is)
Bugs this fixes
(either VSO or GitHub links)
Workarounds, if any
Also, why we think they are insufficient for RC vs. RC2, RC3, or RTW
Risk
This is generally a measure our how central the affected code is to adjacent
scenarios and thus how likely your fix is to destabilize a broader area of code
Performance impact
(with a brief justification for that assessment (e.g. "Low perf impact because no extra allocations/no complexity changes" vs. "Low")
Is this a regression from a previous update?
Root cause analysis
How did we miss it? What tests are we adding to guard against it in the future?
How was the bug found?
(E.g. customer reported it vs. ad hoc testing)
Test documentation updated?
If this is a new non-compiler feature or a significant improvement to an existing feature, update https://github.com/dotnet/roslyn/wiki/Manual-Testing once you know which release it is targeting.