Merge pull request #808 from calumgrant/cs/double-checked-locks

C#: Work on cs/unsafe-double-checked-lock
This commit is contained in:
Tom Hvitved
2019-02-21 11:17:35 +01:00
committed by GitHub
5 changed files with 147 additions and 47 deletions

View File

@@ -11,14 +11,15 @@
| *@name of query (Query ID)* | *Impact on results* | *How/why the query has changed* |
|------------------------------|------------------------|-----------------------------------|
| Off-by-one comparison against container length (cs/index-out-of-bounds) | Fewer false positives | Results have been removed when there are additional guards on the index. |
| Dereferenced variable is always null (cs/dereferenced-value-is-always-null) | Improved results | The query has been rewritten from scratch, and the analysis is now based on static single assignment (SSA) forms. The query is now enabled by default in LGTM. |
| Dereferenced variable may be null (cs/dereferenced-value-may-be-null) | Improved results | The query has been rewritten from scratch, and the analysis is now based on static single assignment (SSA) forms. The query is now enabled by default in LGTM. |
| SQL query built from user-controlled sources (cs/sql-injection), Improper control of generation of code (cs/code-injection), Uncontrolled format string (cs/uncontrolled-format-string), Clear text storage of sensitive information (cs/cleartext-storage-of-sensitive-information), Exposure of private information (cs/exposure-of-sensitive-information) | More results | Data sources have been added from user controls in `System.Windows.Forms`. |
| Use of default ToString() (cs/call-to-object-tostring) | Fewer false positives | Results have been removed for `char` arrays passed to `StringBuilder.Append()`, which were incorrectly marked as using `ToString`. |
| Use of default ToString() (cs/call-to-object-tostring) | Fewer results | Results have been removed when the object is an interface or an abstract class. |
| Unused format argument (cs/format-argument-unused) | Fewer false positives | Results have been removed where the format string is empty. This is often used as a default value and is not an interesting result. |
| Off-by-one comparison against container length (`cs/index-out-of-bounds`) | Fewer false positives | Results have been removed when there are additional guards on the index. |
| Dereferenced variable is always null (`cs/dereferenced-value-is-always-null`) | Improved results | The query has been rewritten from scratch, and the analysis is now based on static single assignment (SSA) forms. The query is now enabled by default in LGTM. |
| Dereferenced variable may be null (`cs/dereferenced-value-may-be-null`) | Improved results | The query has been rewritten from scratch, and the analysis is now based on static single assignment (SSA) forms. The query is now enabled by default in LGTM. |
| SQL query built from user-controlled sources (`cs/sql-injection`), Improper control of generation of code (`cs/code-injection`), Uncontrolled format string (`cs/uncontrolled-format-string`), Clear text storage of sensitive information (`cs/cleartext-storage-of-sensitive-information`), Exposure of private information (`cs/exposure-of-sensitive-information`) | More results | Data sources have been added from user controls in `System.Windows.Forms`. |
| Use of default ToString() (`cs/call-to-object-tostring`) | Fewer false positives | Results have been removed for `char` arrays passed to `StringBuilder.Append()`, which were incorrectly marked as using `ToString`. |
| Use of default ToString() (`cs/call-to-object-tostring`) | Fewer results | Results have been removed when the object is an interface or an abstract class. |
| Unused format argument (`cs/format-argument-unused`) | Fewer false positives | Results have been removed where the format string is empty. This is often used as a default value and is not an interesting result. |
| Double-checked lock is not thread-safe (`cs/unsafe-double-checked-lock`) | Fewer false positives, more true positives | Results have been removed where the underlying field was not updated in the `lock` statement, or where the field is a `struct`. Results have been added where there are other statements inside the `lock` statement. |
## Changes to code extraction
* Fix extraction of `for` statements where the condition declares new variables using `is`.

View File

@@ -1,8 +1,8 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<overview>
<p>Double-checked locking requires that the underlying field is <code>volatile</code>,
otherwise the program can behave incorrectly when running in multiple threads,
<p>Double-checked locking requires that the underlying field is <code>volatile</code>,
otherwise the program can behave incorrectly when running in multiple threads,
for example by computing the field twice.
</p>
</overview>
@@ -16,6 +16,7 @@ for example by computing the field twice.
<li>Make the field volatile using the <code>volatile</code> keyword.</li>
<li>Use the <code>System.Lazy</code> class, which is guaranteed to be thread-safe.
This can often lead to more elegant code.</li>
<li>Use <code>System.Threading.LazyInitializer</code>.</li>
</ol>
</recommendation>
@@ -51,19 +52,23 @@ automatically thread-safe (Recommendation 3):</p>
</example>
<references>
<li>
MSDN: <a href="https://msdn.microsoft.com/en-us/library/ff650316.aspx">Implementing Singleton in C#</a>.
</li>
<li>
MSDN Magazine: <a href="https://msdn.microsoft.com/magazine/jj863136">The C# Memory Model in Theory and Practice</a>.
</li>
<li>
MSDN, C# Reference: <a href="https://msdn.microsoft.com/en-us/library/x13ttww7.aspx">volatile</a>.
</li>
<li>
Wikipedia: <a href="https://en.wikipedia.org/wiki/Double-checked_locking">Double-checked locking</a>.
</li>
<li>
MSDN: <a href="https://docs.microsoft.com/en-us/dotnet/api/system.lazy-1">Lazy&lt;T&gt; Class</a>.
</li>
<li>
MSDN: <a href="https://docs.microsoft.com/en-us/dotnet/api/system.threading.lazyinitializer.ensureinitialized">LazyInitializer.EnsureInitialized Method</a>.
</li>
<li>
MSDN: <a href="https://msdn.microsoft.com/en-us/library/ff650316.aspx">Implementing Singleton in C#</a>.
</li>
<li>
MSDN Magazine: <a href="https://msdn.microsoft.com/magazine/jj863136">The C# Memory Model in Theory and Practice</a>.
</li>
<li>
MSDN, C# Reference: <a href="https://msdn.microsoft.com/en-us/library/x13ttww7.aspx">volatile</a>.
</li>
<li>
Wikipedia: <a href="https://en.wikipedia.org/wiki/Double-checked_locking">Double-checked locking</a>.
</li>
</references>
</qhelp>

View File

@@ -1,10 +1,10 @@
/**
* @name Double-checked lock is not thread-safe
* @description A repeated check on a non-volatile field is not thread-safe, and
* could result in unexpected behavior.
* @description A repeated check on a non-volatile field is not thread-safe on some platforms,
* and could result in unexpected behavior.
* @kind problem
* @problem.severity error
* @precision high
* @precision medium
* @id cs/unsafe-double-checked-lock
* @tags correctness
* concurrency
@@ -22,7 +22,7 @@ class DoubleCheckedLock extends StructuralComparisonConfiguration {
x = unlockedIf.getCondition() and
y = lockedIf.getCondition() and
lock = unlockedIf.getThen().stripSingletonBlocks() and
lockedIf = lock.getBlock().stripSingletonBlocks()
lockedIf.getParent*() = lock.getBlock()
)
}
}
@@ -38,5 +38,7 @@ predicate doubleCheckedLock(Field field, IfStmt ifs) {
from Field field, IfStmt ifs
where
doubleCheckedLock(field, ifs) and
not field.isVolatile()
not field.isVolatile() and
exists(VariableWrite write | write = ifs.getThen().getAChild+() and write.getTarget() = field) and
field.getType() instanceof RefType
select ifs, "Field $@ should be 'volatile' for this double-checked lock.", field, field.getName()

View File

@@ -1,13 +1,17 @@
using System.Collections.Generic;
class Program
{
static object mutex = new object();
static object obj1;
static volatile object obj2;
static bool cond1;
static volatile bool cond2;
object obj3;
bool cond1;
volatile bool cond2;
Coord struct1, struct2;
(int,int) pair1;
static void Main(string[] args)
void Fn()
{
// BAD
if (obj1 == null)
@@ -16,7 +20,7 @@ class Program
{
if (obj1 == null)
{
// ...
obj1 = null;
}
}
}
@@ -25,12 +29,13 @@ class Program
if (obj1 == null)
lock (mutex)
if (obj1 == null)
{
// ...
}
obj1 = null;
// BAD
if (cond1) lock (mutex) if (cond1) { }
// GOOD: A value-type
if (cond1)
lock (mutex)
if (cond1)
cond1 = false;
// GOOD: volatile
if (obj2 == null)
@@ -39,34 +44,117 @@ class Program
{
if (obj2 == null)
{
obj2 = null;
}
}
}
// GOOD: volatile
if (cond2) lock (mutex) if (cond2) { }
if (cond2)
lock (mutex)
if (cond2) { cond2 = false; }
// BAD: FALSE NEGATIVE - not recognized as double-checked lock
// GOOD: not a double-checked lock
if (null == obj1)
{
lock (mutex)
{
if (obj2 == null) { }
if (null == obj2)
obj1 = null;
}
}
// BAD: FALSE NEGATIVE - not recognized as double-checked lock
// BAD (false-positive): not a double-checked lock
// as the condition is not the same.
if (null == obj1)
{
lock (mutex)
{
if (obj1 == null)
obj1 = null;
}
}
// BAD
if (null == obj1)
{
lock (mutex)
{
int x;
if (obj2 == null) { }
if (null == obj1)
obj1 = null;
}
}
// GOOD: not a field
object a = null;
if (a == null) lock (mutex) if (a == null) { }
if (a == null)
lock (mutex)
if (a == null)
a = new object();
// BAD: only obj1 is flagged.
if (obj1 == null && obj2 == null)
{
lock (mutex)
{
if (obj1 == null && obj2 == null)
{
obj1 = null;
}
}
}
// BAD: both obj1 and obj3 are flagged.
if (obj1 == null && obj3 == null)
{
lock (mutex)
{
if (obj1 == null && obj3 == null)
{
obj1 = null;
obj3 = null;
}
}
}
// GOOD: Locking a struct
if (struct1 == struct2)
{
lock(mutex)
{
if (struct1 == struct2)
{
struct1 = new Coord();
}
}
}
// BAD: Field x should be volatile
if (struct1.x is null)
lock (mutex)
if(struct1.x is null)
struct1.x = 3;
// GOOD: Tuples are structs so cannot be volatile.
if(pair1 == (1,2))
{
lock(mutex)
{
if(pair1 == (1,2))
pair1 = (2,3);
}
}
}
}
struct Coord
{
public object x, y;
public static bool operator==(Coord c1, Coord c2) => c1.x==c2.x && c1.y == c2.y;
public static bool operator!=(Coord c1, Coord c2) => !(c1==c2);
}
// semmle-extractor-options: -langversion:latest

View File

@@ -1,3 +1,7 @@
| UnsafeLazyInitialization.cs:13:9:22:9 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:5:19:5:22 | obj1 | obj1 |
| UnsafeLazyInitialization.cs:25:9:30:17 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:5:19:5:22 | obj1 | obj1 |
| UnsafeLazyInitialization.cs:33:9:33:46 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:7:17:7:21 | cond1 | cond1 |
| UnsafeLazyInitialization.cs:17:9:26:9 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:6:19:6:22 | obj1 | obj1 |
| UnsafeLazyInitialization.cs:29:9:32:32 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:6:19:6:22 | obj1 | obj1 |
| UnsafeLazyInitialization.cs:80:9:88:9 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:6:19:6:22 | obj1 | obj1 |
| UnsafeLazyInitialization.cs:98:9:107:9 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:6:19:6:22 | obj1 | obj1 |
| UnsafeLazyInitialization.cs:110:9:120:9 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:6:19:6:22 | obj1 | obj1 |
| UnsafeLazyInitialization.cs:110:9:120:9 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:8:12:8:15 | obj3 | obj3 |
| UnsafeLazyInitialization.cs:135:9:138:34 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:154:19:154:19 | x | x |