Skip to content

Commit

Permalink
Draft
Browse files Browse the repository at this point in the history
  • Loading branch information
antonioaversa committed Mar 3, 2023
1 parent dffe8ff commit 053f7a3
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 13 deletions.
4 changes: 2 additions & 2 deletions analyzers/rspec/cs/S2445_c#.html
Expand Up @@ -3,8 +3,8 @@
<ol>
<li> Locking on a non-<code>readonly</code> field makes it possible for the field’s value to change while a thread is in the code block locked on
the old value. This allows another thread to lock on the new value and access the same block concurrently. </li>
<li> Locking on a local variable or a new instance of an object can undermine synchronization because two different threads running the same method
in parallel will potentially lock on different instances of the same object, allowing them to access the synchronized block at the same time. </li>
<li> Locking on a new instance of an object undermines synchronization because two different threads running the same method in parallel will lock
on different instances of the same object, allowing them to access the synchronized block at the same time. </li>
<li> Locking on a string literal is also dangerous since, depending on whether the string is interned or not, different threads may or may not
synchronize on the same object instance. </li>
</ol>
Expand Down
36 changes: 36 additions & 0 deletions analyzers/rspec/cs/S6507_c#.html
@@ -0,0 +1,36 @@
<p>Locking on a local variable can undermine synchronization because two different threads running the same method in parallel will potentially lock
on different instances of the same object, allowing them to access the synchronized block at the same time.</p>
<h2>Noncompliant Code Example</h2>
<pre>
private void DoSomething()
{
object local;
// ...

lock (local) // Noncompliant
{
// ...
}
}
</pre>
<h2>Compliant Solution</h2>
<pre>
private readonly object lockObj = new object();

private void DoSomething()
{
lock (lockObj)
{
//...
}
}
</pre>
<h2>See</h2>
<ul>
<li> <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/lock">Lock Statement</a> - lock statement - ensure
exclusive access to a shared resource </li>
<li> <a href="https://learn.microsoft.com/en-us/dotnet/api/system.string.intern">String.Intern</a> - <code>String.Intern(String)</code> Method </li>
<li> <a href="https://cwe.mitre.org/data/definitions/412">MITRE, CWE-412</a> - Unrestricted Externally Accessible Lock </li>
<li> <a href="https://cwe.mitre.org/data/definitions/413">MITRE, CWE-413</a> - Improper Resource Locking </li>
</ul>

24 changes: 24 additions & 0 deletions analyzers/rspec/cs/S6507_c#.json
@@ -0,0 +1,24 @@
{
"title": "Blocks should not be synchronized on local variables",
"type": "BUG",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "15min"
},
"tags": [
"cwe",
"multi-threading"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-6507",
"sqKey": "S6507",
"scope": "All",
"securityStandards": {
"CWE": [
412,
413
]
},
"quickfix": "unknown"
}
Expand Up @@ -18,16 +18,20 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using Microsoft.CodeAnalysis;

namespace SonarAnalyzer.Rules.CSharp;

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

private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, "Do not lock on {0}, use a readonly field instead.");
private static readonly DiagnosticDescriptor LockedFieldRule = DescriptorFactory.Create(LockedFieldDiagnosticId, "Do not lock on {0}, use a readonly field instead.");
private static readonly DiagnosticDescriptor LocalVariableRule = DescriptorFactory.Create(LocalVariableDiagnosticId, "Do not lock on local variable '{0}', use a readonly field instead.");

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

protected override void Initialize(SonarAnalysisContext context) =>
context.RegisterNodeAction(CheckLockStatement, SyntaxKind.LockStatement);
Expand All @@ -37,27 +41,24 @@ private static void CheckLockStatement(SonarSyntaxNodeReportingContext context)
var expression = ((LockStatementSyntax)context.Node).Expression?.RemoveParentheses();
if (IsCreation(expression))
{
ReportIssue("a new instance because is a no-op");
context.ReportIssue(Diagnostic.Create(LockedFieldRule, expression.GetLocation(), "a new instance because is a no-op"));
}
else
{
var lazySymbol = new Lazy<ISymbol>(() => context.SemanticModel.GetSymbolInfo(expression).Symbol);
if (IsOfTypeString(expression, lazySymbol))
{
ReportIssue("strings as they can be interned");
context.ReportIssue(Diagnostic.Create(LockedFieldRule, expression.GetLocation(), "strings as they can be interned"));
}
else if (expression is IdentifierNameSyntax && lazySymbol.Value is ILocalSymbol localSymbol)
{
ReportIssue($"local variable '{localSymbol.Name}'");
context.ReportIssue(Diagnostic.Create(LocalVariableRule, expression.GetLocation(), localSymbol.Name));
}
else if (FieldWritable(expression, lazySymbol) is { } field)
{
ReportIssue($"writable field '{field.Name}'");
context.ReportIssue(Diagnostic.Create(LockedFieldRule, expression.GetLocation(), $"writable field '{field.Name}'"));
}
}

void ReportIssue(string message) =>
context.ReportIssue(Diagnostic.Create(Rule, expression.GetLocation(), message));
}

private static bool IsCreation(ExpressionSyntax expression) =>
Expand Down
Expand Up @@ -6431,7 +6431,7 @@ internal static class RuleTypeMappingCS
// ["S6504"],
// ["S6505"],
// ["S6506"],
// ["S6507"],
["S6507"] = "BUG",
// ["S6508"],
// ["S6509"],
// ["S6510"],
Expand Down

0 comments on commit 053f7a3

Please sign in to comment.