Skip to content

Commit 61a3e96

Browse files
yoffaschackmull
andcommitted
java: rewrite conflict detection
- favour unary predicates over binary ones (the natural "conflicting access" is binary) - switch to a dual solution to trade recursion through forall for simple existentials. Co-authored-by: Anders Schack-Mulligen <[email protected]>
1 parent 5109bab commit 61a3e96

File tree

11 files changed

+285
-312
lines changed

11 files changed

+285
-312
lines changed

java/ql/lib/semmle/code/java/ConflictingAccess.qll

Lines changed: 158 additions & 186 deletions
Large diffs are not rendered by default.

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

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,43 @@
1414
import java
1515
import semmle.code.java.ConflictingAccess
1616

17+
predicate unmonitored_access(
18+
ClassAnnotatedAsThreadSafe cls, ExposedFieldAccess a, Expr entry, string msg, string entry_desc
19+
) {
20+
exists(ExposedField f |
21+
cls.unlocked_public_access(f, entry, _, a, true)
22+
or
23+
cls.unlocked_public_access(f, entry, _, a, false) and
24+
cls.has_public_write_access(f)
25+
) and
26+
msg =
27+
"This field access (publicly accessible via $@) is not protected by any monitor, but the class is annotated as @ThreadSafe." and
28+
entry_desc = "this expression"
29+
}
30+
31+
predicate not_fully_monitored_field(
32+
ClassAnnotatedAsThreadSafe cls, ExposedField f, string msg, string cls_name
33+
) {
34+
(
35+
// Technically there has to be a write access for a conflict to exist.
36+
// But if you are locking your reads with different locks, you likely made a typo,
37+
// so in this case we alert without requiring `cls.has_public_write_access(f)`
38+
cls.single_monitor_mismatch(f)
39+
or
40+
cls.not_fully_monitored(f) and
41+
cls.has_public_write_access(f)
42+
) and
43+
msg =
44+
"The field $@ is not properly synchronized in that no single monitor covers all accesses, but the class $@ is annotated as @ThreadSafe." and
45+
cls_name = cls.getName()
46+
}
47+
1748
from
18-
ClassAnnotatedAsThreadSafe cls, FieldAccess modifyingAccess, Expr witness_modifyingAccess,
19-
FieldAccess conflictingAccess, Expr witness_conflictingAccess
49+
ClassAnnotatedAsThreadSafe cls, Top alert_element, Top alert_context, string alert_msg,
50+
string context_desc
2051
where
21-
cls.witness(modifyingAccess, witness_modifyingAccess, conflictingAccess, witness_conflictingAccess)
22-
select modifyingAccess,
23-
"This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor.",
24-
witness_modifyingAccess, "this expression", conflictingAccess, "this field access",
25-
witness_conflictingAccess, "this expression"
26-
// select c, a.getField()
52+
unmonitored_access(cls, alert_element, alert_context, alert_msg, context_desc)
53+
or
54+
not_fully_monitored_field(cls, alert_element, alert_msg, context_desc) and
55+
alert_context = cls
56+
select alert_element, alert_msg, alert_context, context_desc

java/ql/test/query-tests/ThreadSafe/ThreadSafe.expected

Lines changed: 43 additions & 72 deletions
Large diffs are not rendered by default.

java/ql/test/query-tests/ThreadSafe/examples/C.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@ public class C {
1212

1313
public void m() {
1414
this.y = 0; // $ Alert
15-
this.y += 1;
16-
this.y = this.y - 1;
15+
this.y += 1; // $ Alert
16+
this.y = this.y - 1; // $ Alert
1717
}
1818

1919
public void n4() {
20-
this.y = 0;
21-
this.y += 1;
22-
this.y = this.y - 1;
20+
this.y = 0; // $ Alert
21+
this.y += 1; // $ Alert
22+
this.y = this.y - 1; // $ Alert
2323
}
2424

2525
public void setY(int y) {
26-
this.y = y;
26+
this.y = y; // $ Alert
2727
}
2828

2929
public void test() {
30-
if (y == 0) {
30+
if (y == 0) { // $ Alert
3131
lock.lock();
3232
}
33-
y = 0;
33+
y = 0; // $ Alert
3434
lock.unlock();
3535
}
3636

java/ql/test/query-tests/ThreadSafe/examples/FaultyTurnstileExample.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@ class FaultyTurnstileExample {
1010

1111
public void inc() {
1212
lock.lock();
13-
count++; // $ Alert
13+
count++; // $ MISSING: Alert
1414
lock.unlock();
1515
}
1616

1717
public void dec() {
18-
count--;
18+
count--; // $ Alert
1919
}
2020
}
2121

2222
@ThreadSafe
2323
class FaultyTurnstileExample2 {
2424
private Lock lock1 = new ReentrantLock();
2525
private Lock lock2 = new ReentrantLock();
26-
private int count = 0;
26+
private int count = 0; // $ Alert
2727

2828
public void inc() {
2929
lock1.lock();
30-
count++; // $ Alert
30+
count++;
3131
lock1.unlock();
3232
}
3333

java/ql/test/query-tests/ThreadSafe/examples/FlawedSemaphore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public FlawedSemaphore(int c) {
1212

1313
public void acquire() {
1414
try {
15-
while (state == capacity) {
15+
while (state == capacity) { // $ Alert
1616
this.wait();
1717
}
1818
state++; // $ Alert

java/ql/test/query-tests/ThreadSafe/examples/LockExample.java

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ public class LockExample {
1515
private Lock lock1 = new ReentrantLock();
1616
private Lock lock2 = new ReentrantLock();
1717

18-
private int length = 0;
19-
private int notRelatedToOther = 10;
20-
private int[] content = new int[10];
18+
private int length = 0; // $ Alert
19+
private int notRelatedToOther = 10; // $ Alert
20+
private int[] content = new int[10]; // $ Alert
2121

2222
public void add(int value) {
2323
lock1.lock();
24-
length++; // $ Alert
25-
content[length] = value; // $ Alert
24+
length++;
25+
content[length] = value;
2626
lock1.unlock();
2727
}
2828

@@ -35,18 +35,18 @@ public void removeCorrect() {
3535

3636
public void notTheSameLockAsAdd() { // use locks, but different ones
3737
lock2.lock();
38-
length--; // $ Alert
39-
content[length] = 0; // $ Alert
38+
length--;
39+
content[length] = 0;
4040
lock2.unlock();
4141
}
4242

4343
public void noLock() { // no locks
44-
length--;
45-
content[length] = 0;
44+
length--; // $ Alert
45+
content[length] = 0; // $ Alert
4646
}
4747

4848
public void fieldUpdatedOutsideOfLock() { // adjusts length without lock
49-
length--;
49+
length--; // $ Alert
5050

5151
lock1.lock();
5252
content[length] = 0;
@@ -59,30 +59,30 @@ public synchronized void synchronizedLock() { // no locks, but with synchronized
5959
}
6060

6161
public void onlyLocked() { // never unlocked, only locked
62-
length--;
62+
length--; // $ Alert
6363

6464
lock1.lock();
65-
content[length] = 0;
65+
content[length] = 0; // $ Alert
6666
}
6767

6868
public void onlyUnlocked() { // never locked, only unlocked
69-
length--;
69+
length--; // $ Alert
7070

71-
content[length] = 0;
71+
content[length] = 0; // $ Alert
7272
lock1.unlock();
7373
}
7474

7575
public void notSameLock() {
76-
length--;
76+
length--; // $ Alert
7777

7878
lock2.lock();// Not the same lock
79-
content[length] = 0;
79+
content[length] = 0; // $ Alert
8080
lock1.unlock();
8181
}
8282

8383
public void updateCount() {
8484
lock2.lock();
85-
notRelatedToOther++; // $ Alert
85+
notRelatedToOther++;
8686
lock2.unlock();
8787
}
8888

@@ -91,7 +91,7 @@ public void updateCountTwiceCorrect() {
9191
notRelatedToOther++;
9292
lock2.unlock();
9393
lock1.lock();
94-
notRelatedToOther++; // $ Alert
94+
notRelatedToOther++;
9595
lock1.unlock();
9696
}
9797

@@ -109,19 +109,19 @@ public void updateCountTwiceLock() {
109109
notRelatedToOther++;
110110
lock2.unlock();
111111
lock1.lock();
112-
notRelatedToOther++;
112+
notRelatedToOther++; // $ Alert
113113
}
114114

115115
public void updateCountTwiceUnLock() {
116116
lock2.lock();
117117
notRelatedToOther++;
118118
lock2.unlock();
119-
notRelatedToOther++;
119+
notRelatedToOther++; // $ Alert
120120
lock1.unlock();
121121
}
122122

123123
public void synchronizedNonRelatedOutside() {
124-
notRelatedToOther++;
124+
notRelatedToOther++; // $ Alert
125125

126126
synchronized(this) {
127127
length++;
@@ -142,15 +142,15 @@ public void synchronizedNonRelatedOutside3() {
142142
length++;
143143
}
144144

145-
notRelatedToOther = 1;
145+
notRelatedToOther = 1; // $ Alert
146146
}
147147

148148
public void synchronizedNonRelatedOutside4() {
149149
synchronized(lock1) {
150150
length++;
151151
}
152152

153-
notRelatedToOther = 1;
153+
notRelatedToOther = 1; // $ Alert
154154
}
155155

156156
}

java/ql/test/query-tests/ThreadSafe/examples/SyncLstExample.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ public FaultySyncLstExample(List<T> lst) {
3737

3838
public void add(T item) {
3939
lock.lock();
40-
lst.add(item); // $ Alert
40+
lst.add(item);
4141
lock.unlock();
4242
}
4343

4444
public void remove(int i) {
45-
lst.remove(i);
45+
lst.remove(i); // $ Alert
4646
}
4747
}

java/ql/test/query-tests/ThreadSafe/examples/SyncStackExample.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ class FaultySyncStackExample<T> {
2929

3030
public void push(T item) {
3131
lock.lock();
32-
stc.push(item); // $ Alert
32+
stc.push(item);
3333
lock.unlock();
3434
}
3535

3636
public void pop() {
37-
stc.pop();
37+
stc.pop(); // $ Alert
3838
}
3939
}

java/ql/test/query-tests/ThreadSafe/examples/SynchronizedAndLock.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
public class SynchronizedAndLock {
88
private Lock lock = new ReentrantLock();
99

10-
private int length = 0;
10+
private int length = 0; // $ Alert
1111

1212
public void add(int value) {
1313
lock.lock();
14-
length++; // $ Alert
14+
length++;
1515
lock.unlock();
1616
}
1717

0 commit comments

Comments
 (0)