C#: Fixes to double-checked lock.

This commit is contained in:
calum
2019-01-21 15:11:58 +00:00
parent 937049e060
commit 7addd41e38
3 changed files with 115 additions and 19 deletions

View File

@@ -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
not field.getType() instanceof Struct
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) { }
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 == 2)
lock (mutex)
if(struct1.x == 2)
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 int 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,9 @@
| 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:35:9:38:34 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:9:10:9:14 | cond1 | cond1 |
| 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:16:154:16 | x | x |
| UnsafeLazyInitialization.cs:141:9:148:9 | if (...) ... | Field $@ should be 'volatile' for this double-checked lock. | UnsafeLazyInitialization.cs:12:15:12:19 | pair1 | pair1 |