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

Warn for yield return in lock #72755

Merged
8 changes: 8 additions & 0 deletions docs/compilers/CSharp/Warnversion Warning Waves.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ In a typical project, this setting is controlled by the `AnalysisLevel` property
which determines the `WarningLevel` property (passed to the `Csc` task).
For more information on `AnalysisLevel`, see https://devblogs.microsoft.com/dotnet/automatically-find-latent-bugs-in-your-code-with-net-5/

## Warning level 9

The compiler shipped with .NET 9 (the C# 13 compiler) contains the following warnings which are reported only under `/warn:9` or higher.

| Warning ID | Description |
|------------|-------------|
| CS9230 | ['yield return' should not be used in the body of a lock statement](https://github.com/dotnet/roslyn/issues/72443) |

## Warning level 8

The compiler shipped with .NET 8 (the C# 12 compiler) contains the following warnings which are reported only under `/warn:8` or higher.
Expand Down
21 changes: 2 additions & 19 deletions src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,25 +648,8 @@ resultOperatorKind is BinaryOperatorKind.ObjectEqual or BinaryOperatorKind.Objec

if (needsFilterDiagnostics)
{
Debug.Assert(conversionDiagnostics != diagnostics);
diagnostics.AddDependencies(conversionDiagnostics);

var sourceBag = conversionDiagnostics.DiagnosticBag;
Debug.Assert(sourceBag is not null);

if (!sourceBag.IsEmptyWithoutResolution)
{
foreach (var diagnostic in sourceBag.AsEnumerableWithoutResolution())
{
var code = diagnostic is DiagnosticWithInfo { HasLazyInfo: true, LazyInfo.Code: var lazyCode } ? lazyCode : diagnostic.Code;
if ((ErrorCode)code is not ErrorCode.WRN_ConvertingLock)
{
diagnostics.Add(diagnostic);
}
}
}

conversionDiagnostics.Free();
conversionDiagnostics.CopyFilteredToAndFree(diagnostics,
static code => code is not ErrorCode.WRN_ConvertingLock);
}

resultConstant = FoldBinaryOperator(node, resultOperatorKind, resultLeft, resultRight, resultType, diagnostics);
Expand Down
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ private BoundStatement BindYieldReturnStatement(YieldStatementSyntax node, Bindi
{
Error(diagnostics, ErrorCode.ERR_YieldNotAllowedInScript, node.YieldKeyword);
}
else if (this.Flags.Includes(BinderFlags.InLockBody))
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2024

Choose a reason for hiding this comment

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

else if (this.Flags.Includes(BinderFlags.InLockBody))

It is very easy to make a mistake and start adding checks for other errors (not warnings) in the else of this if statement. Consider adding a comment right above it saying that all error conditions should be checked outside its else if one is added in the future. #Closed

{
Error(diagnostics, ErrorCode.WRN_BadYieldInLock, node.YieldKeyword);
}

CheckRequiredLangVersionForIteratorMethods(node, diagnostics);
return new BoundYieldReturnStatement(node, argument);
Expand Down
26 changes: 26 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/BindingDiagnosticBag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;

Expand Down Expand Up @@ -185,5 +186,30 @@ internal void Add(DiagnosticInfo? info, Location location)
DiagnosticBag?.Add(info, location);
}
}

internal void CopyFilteredToAndFree(BindingDiagnosticBag target, Func<ErrorCode, bool> predicate)
{
Debug.Assert(target != this);
target.AddDependencies(this);

var bag = DiagnosticBag;
Debug.Assert(bag is not null);

if (bag?.IsEmptyWithoutResolution == false)
{
foreach (var diagnostic in bag.AsEnumerableWithoutResolution())
{
var code = diagnostic is DiagnosticWithInfo { HasLazyInfo: true, LazyInfo.Code: var lazyCode }
? lazyCode
: diagnostic.Code;
if (predicate((ErrorCode)code))
{
target.Add(diagnostic);
}
}
}

Free();
}
}
}
17 changes: 14 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/LockBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ internal override BoundStatement BindLockStatementParts(BindingDiagnosticBag dia
hasErrors = true;
}

if (exprType?.IsWellKnownTypeLock() == true &&
TryFindLockTypeInfo(exprType, diagnostics, exprSyntax) is { } lockTypeInfo)
bool isLockObjectBased = exprType?.IsWellKnownTypeLock() == true;
if (isLockObjectBased &&
TryFindLockTypeInfo(exprType!, diagnostics, exprSyntax) is { } lockTypeInfo)
{
CheckFeatureAvailability(exprSyntax, MessageID.IDS_FeatureLockObject, diagnostics);

Expand All @@ -73,8 +74,18 @@ internal override BoundStatement BindLockStatementParts(BindingDiagnosticBag dia
errorCode: ErrorCode.ERR_BadSpecialByRefLock);
}

BoundStatement stmt = originalBinder.BindPossibleEmbeddedStatement(_syntax.Statement, diagnostics);
var needsFilterDiagnostics = isLockObjectBased && diagnostics.AccumulatesDiagnostics;
var bodyDiagnostics = needsFilterDiagnostics ? BindingDiagnosticBag.GetInstance(template: diagnostics) : diagnostics;

BoundStatement stmt = originalBinder.BindPossibleEmbeddedStatement(_syntax.Statement, bodyDiagnostics);
Debug.Assert(this.Locals.IsDefaultOrEmpty);

if (needsFilterDiagnostics)
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2024

Choose a reason for hiding this comment

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

needsFilterDiagnostics

Could you point me to the test that observes effect of this filtering? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

LockTests.Yield and Yield_Async

{
bodyDiagnostics.CopyFilteredToAndFree(diagnostics,
static code => code is not ErrorCode.WRN_BadYieldInLock);
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2024

Choose a reason for hiding this comment

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

While it feels tempting to always report a warning and then filter it out, I would prefer us to not report the warning in the first place. A diagnostic filtering is an exception rather than a usual way of doing things. In this case, I think, we can avoid it. See, for example, how a set of LockedOrDisposedVariables is exposed and used. I think the fact whether we are in context of a "modern" lock could be accessed/exposed in a similar way, and we could simply not report the warning in the first place. #Closed

Copy link
Contributor

Choose a reason for hiding this comment

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

Another thing to consider is to simplify and not attempt to suppress the warning at all. I do not think it is going to lead to any confusion and the problem with the lock object itself is a separate matter.

}

return new BoundLockStatement(_syntax, expr, stmt, hasErrors);
}

Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -7905,4 +7905,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_NoModifiersOnUsing" xml:space="preserve">
<value>Modifiers cannot be placed on using declarations</value>
</data>
<data name="WRN_BadYieldInLock" xml:space="preserve">
<value>'yield return' should not be used in the body of a lock statement</value>
</data>
<data name="WRN_BadYieldInLock_Title" xml:space="preserve">
<value>'yield return' should not be used in the body of a lock statement</value>
</data>
</root>
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2304,6 +2304,8 @@ internal enum ErrorCode

#endregion

WRN_BadYieldInLock = 9230,

// Note: you will need to do the following after adding warnings:
// 1) Re-generate compiler code (eng\generate-compiler-code.cmd).
}
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ internal static int GetWarningLevel(ErrorCode code)
// docs/compilers/CSharp/Warnversion Warning Waves.md
switch (code)
{
case ErrorCode.WRN_BadYieldInLock:
// Warning level 9 is exclusively for warnings introduced in the compiler
// shipped with dotnet 9 (C# 13) and that can be reported for pre-existing code.
return 9;
case ErrorCode.WRN_AddressOfInAsync:
case ErrorCode.WRN_ByValArraySizeConstRequired:
// Warning level 8 is exclusively for warnings introduced in the compiler
Expand Down Expand Up @@ -2431,6 +2435,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.ERR_ParamsCollectionExtensionAddMethod:
case ErrorCode.ERR_ParamsCollectionMissingConstructor:
case ErrorCode.ERR_NoModifiersOnUsing:
case ErrorCode.WRN_BadYieldInLock:
return false;
default:
// NOTE: All error codes must be explicitly handled in this switch statement
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading