Skip to content

Commit

Permalink
Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
antonioaversa committed Feb 13, 2023
1 parent 5c33344 commit 94d1887
Show file tree
Hide file tree
Showing 9 changed files with 355 additions and 66 deletions.
31 changes: 18 additions & 13 deletions analyzers/rspec/cs/S2445_c#.html
@@ -1,30 +1,35 @@
<p>Synchronizing on a class field synchronizes not on the field itself, but on the object assigned to it. So synchronizing on a non-<code>final</code>
field makes it possible for the field’s value to change while a thread is in a block synchronized on the old value. That would allow a second thread,
synchronized on the new value, to enter the block at the same time.</p>
<p>The story is very similar for synchronizing on parameters; two different threads running the method in parallel could pass two different object
instances in to the method as parameters, completely undermining the synchronization.</p>
<p>Locking on a class field synchronizes not on the field itself, but on the object assigned to it. So locking on a non-<code>readonly</code> field
makes it possible for the field’s value to change while a thread is in a block locked on the old value. That would allow a second thread, locked on
the new value, to enter the block at the same time.</p>
<p>The story is very similar for locking on a local variable or on a new instance; two different threads running the method in parallel would lock on
two different object instances, completely undermining the synchronization.</p>
<h2>Noncompliant Code Example</h2>
<pre>
private String color = "red";
private string color = "red";

private void doSomething(){
synchronized(color) { // Noncompliant; lock is actually on object instance "red" referred to by the color variable
private void DoSomething()
{
lock (color) // Noncompliant; lock is actually on object instance "red" referred to by the color variable
{
//...
color = "green"; // other threads now allowed into this block
// ...
}
synchronized(new Object()) { // Noncompliant this is a no-op.
lock (new object()) // Noncompliant this is a no-op.
{
// ...
}
}
</pre>
<h2>Compliant Solution</h2>
<pre>
private String color = "red";
private final Object lockObj = new Object();
private string color = "red";
private readonly object lockObj = new object();

private void doSomething(){
synchronized(lockObj) {
private void DoSomething()
{
lock (lockObj)
{
//...
color = "green";
// ...
Expand Down
2 changes: 1 addition & 1 deletion analyzers/rspec/cs/S2445_c#.json
@@ -1,5 +1,5 @@
{
"title": "Blocks should be synchronized on \"private final\" fields",
"title": "Blocks should be synchronized on \"private readonly\" fields",
"type": "BUG",
"status": "ready",
"remediation": {
Expand Down
@@ -0,0 +1,61 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Rules.CSharp;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class LockedFieldPrivateReadonly : SonarDiagnosticAnalyzer
{
private const string DiagnosticId = "S2445";

private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, "{0}");

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

protected override void Initialize(SonarAnalysisContext context) =>
context.RegisterNodeAction(c =>
{
var expression = ((LockStatementSyntax)c.Node).Expression;
if (expression is ObjectCreationExpressionSyntax
or AnonymousObjectCreationExpressionSyntax
or ArrayCreationExpressionSyntax
or ImplicitArrayCreationExpressionSyntax
or QueryExpressionSyntax)
{
c.ReportIssue(Diagnostic.Create(Rule, expression.GetLocation(), "Locking on a new instance is a no-op."));
}
else if (expression.IsAnyKind(SyntaxKind.StringLiteralExpression, SyntaxKind.InterpolatedStringExpression))
{
c.ReportIssue(Diagnostic.Create(Rule, expression.GetLocation(), "Strings can be interned, and should not be used for locking."));
}
else if (expression is IdentifierNameSyntax
&& c.SemanticModel.GetSymbolInfo(expression).Symbol is ILocalSymbol lockedSymbol)
{
c.ReportIssue(Diagnostic.Create(Rule, expression.GetLocation(), $"'{lockedSymbol.Name}' is a local variable, and should not be used for locking."));
}
else if (expression is (IdentifierNameSyntax or MemberAccessExpressionSyntax)
&& c.SemanticModel.GetSymbolInfo(expression).Symbol is IFieldSymbol lockedField
&& (!lockedField.IsReadOnly || lockedField.GetEffectiveAccessibility() != Accessibility.Private))
{
c.ReportIssue(Diagnostic.Create(Rule, expression.GetLocation(), $"'{lockedField.Name}' is not 'private readonly', and should not be used for locking."));
}
},
SyntaxKind.LockStatement);
}

This file was deleted.

Expand Up @@ -23,11 +23,25 @@
namespace SonarAnalyzer.UnitTest.Rules;

[TestClass]
public class SynchronizedFieldAssignmentTest
public class LockedFieldPrivateReadonlyTest
{
private readonly VerifierBuilder builder = new VerifierBuilder<SynchronizedFieldAssignment>();
private readonly VerifierBuilder builder = new VerifierBuilder<LockedFieldPrivateReadonly>();

[TestMethod]
public void SynchronizedFieldAssignment_CS() =>
builder.AddPaths("SynchronizedFieldAssignment.cs").Verify();
public void LockedFieldPrivateReadonly_CS() =>
builder.AddPaths("LockedFieldPrivateReadonly.cs").Verify();

[TestMethod]
public void LockedFieldPrivateReadonly_CSharp8() =>
builder
.AddPaths("LockedFieldPrivateReadonly.CSharp8.cs")
.WithOptions(ParseOptionsHelper.FromCSharp8)
.Verify();

[TestMethod]
public void LockedFieldPrivateReadonly_CSharp11() =>
builder
.AddPaths("LockedFieldPrivateReadonly.CSharp11.cs")
.WithOptions(ParseOptionsHelper.FromCSharp11)
.Verify();
}
@@ -0,0 +1,21 @@
using System;

class Test
{
static readonly object staticReadonlyField = null;
static object staticReadWriteField = null;

readonly object readonlyField = null;
object readWriteField = null;

void OnANewInstance()
{
lock ("""a raw string literal""") { } // Noncompliant
lock ($"""an interpolated {"raw string literal"}""") { } // Noncompliant
}

void TargetTypedObjectCreation()
{
lock (new() as Tuple<int>) { } // Error [CS8754]
}
}
@@ -0,0 +1,56 @@
using System;

class Test
{
static readonly object staticReadonlyField = null;
static object staticReadWriteField = null;

readonly object readonlyField = null;
object readWriteField = null;

Test()
{
ref object refToReadonlyField = ref readonlyField;
lock (refToReadonlyField) { } // Noncompliant, while the reference is to a readonly field, the reference itself is a local variable and as of C# 7.3 can be ref reassigned

ref object refToReadWriteField = ref readWriteField;
lock (refToReadWriteField) { } // Noncompliant
}

void ReadonlyReferences()
{
lock (RefReturnReadonlyField(this)) { }
lock (RefReturnStaticReadonlyField()) { }
lock (StaticRefReturnReadonlyField(this)) { }
lock (StaticRefReturnStaticReadonlyField()) { }

ref readonly object RefReturnReadonlyField(Test instance) => ref instance.readonlyField;
ref readonly object RefReturnStaticReadonlyField() => ref Test.staticReadonlyField;
static ref readonly object StaticRefReturnReadonlyField(Test instance) => ref instance.readonlyField;
static ref readonly object StaticRefReturnStaticReadonlyField() => ref Test.staticReadonlyField;
}

void OnANewInstanceOnStack()
{
lock (stackalloc int[] { }) { } // Error [CS0185]
lock (stackalloc [] { 1 }) { } // Error [CS0185]
}

void CoalescingAssignment(object oPar)
{
lock (oPar ??= readonlyField) { } // FN, null conditional assignment not supported
}
}

class NonPrivateAccessibily
{
private protected readonly object privateProtectedField = null;

private object PrivateProtectedProperty => null;

void Test()
{
lock (privateProtectedField) { } // Noncompliant
lock (PrivateProtectedProperty) { } // Compliant, not a field
}
}

0 comments on commit 94d1887

Please sign in to comment.