Skip to content

Commit 944c082

Browse files
committed
Java: Fix FP in DoubleCheckedLocking.ql
1 parent 04c1502 commit 944c082

File tree

6 files changed

+90
-1
lines changed

6 files changed

+90
-1
lines changed

change-notes/1.20/analysis-java.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
| **Query** | **Expected impact** | **Change** |
1616
|----------------------------|------------------------|------------------------------------------------------------------|
17+
| Double-checked locking is not thread-safe (`java/unsafe-double-checked-locking`) | Fewer false positive results | Results that use safe publication through a `final` field are no longer reported. |
1718
| Result of multiplication cast to wider type (`java/integer-multiplication-cast-to-long`) | Fewer results | Results involving conversions to `float` or `double` are no longer reported, as they were almost exclusively false positives. |
1819

1920
## Changes to QL libraries

java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.ql

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,21 @@
1515
import java
1616
import DoubleCheckedLocking
1717

18+
predicate allFieldsFinal(Class c) { forex(Field f | c.inherits(f) | f.isFinal()) }
19+
1820
from IfStmt if1, IfStmt if2, SynchronizedStmt sync, Field f
1921
where
2022
doubleCheckedLocking(if1, if2, sync, f) and
21-
not f.isVolatile()
23+
not f.isVolatile() and
24+
not (
25+
// Non-volatile double-checked locking is ok when the object is immutable and
26+
// there is only a single non-synchronized field read.
27+
allFieldsFinal(f.getType()) and
28+
1 = strictcount(FieldAccess fa |
29+
fa.getField() = f and
30+
fa.getEnclosingCallable() = sync.getEnclosingCallable() and
31+
not fa.getEnclosingStmt().getParent*() = sync.getBlock()
32+
)
33+
)
2234
select sync, "Double-checked locking on the non-volatile field $@ is not thread-safe.", f,
2335
f.toString()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
private Object lock = new Object();
2+
private MyImmutableObject f = null;
3+
4+
public MyImmutableObject getMyImmutableObject() {
5+
if (f == null) {
6+
synchronized(lock) {
7+
if (f == null) {
8+
f = new MyImmutableObject();
9+
}
10+
}
11+
}
12+
return f; // BAD
13+
}

java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLockingShared.qhelp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ variable can be used to avoid reading the field more times than neccessary.
6666
</p>
6767
<sample src="DoubleCheckedLockingGood.java"/>
6868

69+
<p>
70+
As a final note, it is possible to use double-checked locking correctly without
71+
<code>volatile</code> if the constructed object is immutable in the sense that
72+
all its fields are declared <code>final</code> and the double-checked field is
73+
read exactly once outside the synchronized block.
74+
</p>
75+
<p>
76+
Given that all fields in <code>MyImmutableObject</code> are declared
77+
<code>final</code> then the following example is protected against exposing
78+
uninitialized fields to another thread. However, since there are two reads of
79+
<code>f</code> without synchronization, it is possible that these are
80+
reordered, which means that this method can return <code>null</code>.
81+
</p>
82+
<sample src="DoubleCheckedLockingBad3.java"/>
83+
<p>
84+
In this case, using a local variable to minimize the number of field reads is
85+
no longer a performance improvement, but rather a crucial detail that is
86+
necessary for correctness.
87+
</p>
88+
6989
</example>
7090

7191
<references>
@@ -80,6 +100,12 @@ Java Language Specification:
80100
<li>
81101
Wikipedia: <a href="https://en.wikipedia.org/wiki/Double-checked_locking">Double-checked locking</a>.
82102
</li>
103+
<li>
104+
<a href="https://shipilev.net/blog/2014/safe-public-construction/">Safe Publication and Safe Initialization in Java</a>.
105+
</li>
106+
<li>
107+
<a href="https://shipilev.net/blog/2016/close-encounters-of-jmm-kind/">Close Encounters of The Java Memory Model Kind</a>.
108+
</li>
83109

84110
</references>
85111

java/ql/test/query-tests/DoubleCheckedLocking/A.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,40 @@ public B getter4() {
7272
}
7373
return b4;
7474
}
75+
76+
static class FinalHelper<T> {
77+
public final T x;
78+
public FinalHelper(T x) {
79+
this.x = x;
80+
}
81+
}
82+
83+
private FinalHelper<B> b5;
84+
public B getter5() {
85+
if (b5 == null) {
86+
synchronized(this) {
87+
if (b5 == null) {
88+
B b = new B();
89+
b5 = new FinalHelper<B>(b); // BAD, racy read on b5 outside synchronized-block
90+
}
91+
}
92+
}
93+
return b5.x; // Potential NPE here, as the two b5 reads may be reordered
94+
}
95+
96+
private FinalHelper<B> b6;
97+
public B getter6() {
98+
FinalHelper<B> a = b6;
99+
if (a == null) {
100+
synchronized(this) {
101+
a = b6;
102+
if (a == null) {
103+
B b = new B();
104+
a = new FinalHelper<B>(b);
105+
b6 = a; // OK, published through final field with a single non-synced read
106+
}
107+
}
108+
}
109+
return a.x;
110+
}
75111
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
| A.java:25:7:30:7 | stmt | Double-checked locking on the non-volatile field $@ is not thread-safe. | A.java:21:13:21:14 | b1 | b1 |
2+
| A.java:86:7:91:7 | stmt | Double-checked locking on the non-volatile field $@ is not thread-safe. | A.java:83:26:83:27 | b5 | b5 |

0 commit comments

Comments
 (0)