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

Implement RS0042 (Do not copy value) #3420

Merged
merged 17 commits into from
Apr 19, 2020
Merged

Conversation

sharwell
Copy link
Member

@sharwell sharwell commented Mar 23, 2020

At a high level, this pull request approaches non-copyable value type restrictions by preventing all use of such types (similar to Banned API analyzer), followed by selectively allowing known safe patterns.

For the implementation, I have been iterating over Roslyn.sln using AnalyzerRunner to log all diagnostics produced by this analyzer.

.\artifacts\bin\AnalyzerRunner\Release\netcoreapp3.1\AnalyzerRunner.exe ..\roslyn-analyzers\artifacts\bin\Roslyn.Diagnostics.Analyzers\Debug\netstandard2.0\ .\Roslyn.sln /stats /concurrent /a DoNotCopyValue /log DoNotCopyValue.txt

Supersedes #2831

helpLinkUri: null,
customTags: WellKnownDiagnosticTags.Telemetry);

internal static DiagnosticDescriptor NoBoxingRule = new DiagnosticDescriptor(
Copy link
Member Author

Choose a reason for hiding this comment

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

📝 Originally the analyzer reported all boxing of non-copyable types. This was particularly problematic for struct enumerators, so the rule was subsequently modified to allow moving a value into a box, and blocking the unboxing. Boxed non-copyable values may be used via Unsafe.Unbox<T>, which returns a reference to the boxed value without copying it out of the box.

helpLinkUri: null,
customTags: WellKnownDiagnosticTags.Telemetry);

internal static DiagnosticDescriptor UnsupportedUseRule = new DiagnosticDescriptor(
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 rule is reported for cases where a non-copyable value type is used in a manner that the analyzer does not understand. Eventually, we want more specific diagnostics to be reported for invalid use cases, and no diagnostics to be reported for valid use cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Will this rule be removed before you merge this PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. It can be removed if we ever get to a case where all the potential copies are covered by other messages.

@codecov
Copy link

codecov bot commented Mar 23, 2020

Codecov Report

Merging #3420 into master will decrease coverage by 0.19%.
The diff coverage is 66.32%.

@@            Coverage Diff             @@
##           master    #3420      +/-   ##
==========================================
- Coverage   95.27%   95.08%   -0.20%     
==========================================
  Files        1043     1045       +2     
  Lines      235509   237064    +1555     
  Branches    15244    15365     +121     
==========================================
+ Hits       224374   225404    +1030     
- Misses       9461     9962     +501     
- Partials     1674     1698      +24     

@jkotas
Copy link
Member

jkotas commented Mar 24, 2020

This looks very similar to https://github.com/ufcpp/NonCopyableAnalyzer . What do you think about using an attribute to mark the non-copyable structs?

@sharwell
Copy link
Member Author

@jkotas The manner in which non-copyable types are identified is separate from how they are handled after identification. For now, I'm experimenting at a rather large scale in Roslyn.sln by treating all mutable value types as non-copyable.

@mavasani
Copy link
Contributor

FYI: You will need to fix a new RS diagnostic once #3444 goes in.

context.RegisterCompilationStartAction(context =>
{
var cache = new NonCopyableTypesCache(context.Compilation);
context.RegisterOperationBlockAction(context => AnalyzeOperationBlock(context, cache));
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 needs to also have a symbol analyzer to ensure non-copyable types are not used incorrectly (e.g. the early checks would be for the use of a non-copyable type as a generic argument).


public override void VisitAddressOf(IAddressOfOperation operation)
{
CheckTypeInUnsupportedContext(operation);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you override every single method on OperationWalker, with this being the default implementation?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. I do that until I manually update the method with a more complete implementation.

Copy link
Contributor

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

Overall LGTM. I can try to work on a follow-up to write this on top of our DFA framework - we can measure the complexity vs perf characteristics of both approach.

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.

3 participants