Skip to content

Commit 886f5b2

Browse files
committed
java: implement SCC contraction of the call graph
Our monitor analysis would be fooled by cycles in the call graph, since it required all edges on a path to a conflicting access to be either - targetting a method where the access is monitored (recursively) or - monitored locally, that is the call is monitored in the calling method For access to be monitored (first case) all outgoing edges (towards an access) need to satisfy this property. For a loop, that is too strong, only edges out of the loop actually need to be protected. This led to FPs.
1 parent 7e68d40 commit 886f5b2

File tree

3 files changed

+105
-12
lines changed

3 files changed

+105
-12
lines changed

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

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,18 +261,113 @@ class ClassAnnotatedAsThreadSafe extends Class {
261261
)
262262
}
263263

264-
/** Holds if all paths from `m` to `a` are monitored by `monitor`. */
265-
predicate monitorsVia(Method m, ExposedFieldAccess a, Monitors::Monitor monitor) {
264+
// NOTE:
265+
// In order to deal with loops in the call graph, we compute the strongly connected components (SCCs).
266+
// We only wish to do this for the methods that can lead to exposed field accesses.
267+
// Given a field access `a`, we can consider a "call graph of interest", a sub graph of the call graph
268+
// that only contains methods that lead to an access of `a`. We call this "the call graph induced by `a`".
269+
// We can then compute the SCCs of this graph, yielding the SCC graph induced by `a`.
270+
//
271+
/**
272+
* Holds if a call to method `m` can cause an access of `a` by `m` calling `callee`.
273+
* This is an edge in the call graph induced by `a`.
274+
*/
275+
predicate accessVia(Method m, ExposedFieldAccess a, Method callee) {
276+
exists(MethodCall c | this.providesAccess(m, c, a) | callee = c.getCallee())
277+
}
278+
279+
/** Holds if `m` can reach `reached` by a path in the call graph induced by `a`. */
280+
predicate accessReach(Method m, ExposedFieldAccess a, Method reached) {
281+
m = this.getAMethod() and
282+
reached = this.getAMethod() and
283+
this.providesAccess(m, _, a) and
284+
this.providesAccess(reached, _, a) and
285+
(
286+
this.accessVia(m, a, reached)
287+
or
288+
exists(Method mid | this.accessReach(m, a, mid) | this.accessVia(mid, a, reached))
289+
)
290+
}
291+
292+
/**
293+
* Holds if `rep` is a representative of the SCC containing `m` in the call graph induced by `a`.
294+
* This only assigns representatives to methods involved in loops.
295+
* To get a representative of any method, use `repSCC`.
296+
*/
297+
predicate repInLoopSCC(Method rep, ExposedFieldAccess a, Method m) {
298+
// `rep` and `m` are in the same SCC
299+
this.accessReach(rep, a, m) and
300+
this.accessReach(m, a, rep) and
301+
// `rep` is the representative of the SCC
302+
// that is, the earliest in the source code
303+
forall(Method alt_rep |
304+
rep != alt_rep and
305+
this.accessReach(alt_rep, a, m) and
306+
this.accessReach(m, a, alt_rep)
307+
|
308+
rep.getLocation().getStartLine() < alt_rep.getLocation().getStartLine()
309+
)
310+
}
311+
312+
/** Holds if `rep` is a representative of the SCC containing `m` in the call graph induced by `a`. */
313+
predicate repSCC(Method rep, ExposedFieldAccess a, Method m) {
314+
this.repInLoopSCC(rep, a, m)
315+
or
266316
m = this.getAMethod() and
267317
this.providesAccess(m, _, a) and
268-
(a.getEnclosingCallable() = m implies Monitors::locallyMonitors(a, monitor)) and
269-
forall(MethodCall c |
270-
c.getEnclosingCallable() = m and
271-
this.providesAccess(c.getCallee(), _, a)
318+
not exists(Method r | this.repInLoopSCC(r, a, m)) and
319+
rep = m
320+
}
321+
322+
/**
323+
* Holds if `c` is a call from the SCC represented by `callerRep` to the (different) SCC represented by `calleeRep`.
324+
* This is an edge in the SCC graph induced by `a`.
325+
*/
326+
predicate callEdgeSCC(Method callerRep, ExposedFieldAccess a, MethodCall c, Method calleeRep) {
327+
callerRep != calleeRep and
328+
exists(Method caller, Method callee |
329+
this.repSCC(callerRep, a, caller) and
330+
this.repSCC(calleeRep, a, callee)
272331
|
332+
this.accessVia(caller, a, callee) and
333+
c.getEnclosingCallable() = caller and
334+
c.getCallee() = callee
335+
)
336+
}
337+
338+
/**
339+
* Holds if the SCC represented by `rep` can cause an access to `a` and `e` is the expression that leads to that access.
340+
* `e` will either be `a` itself or a method call that leads to `a` via a different SCC.
341+
*/
342+
predicate providesAccessSCC(Method rep, Expr e, ExposedFieldAccess a) {
343+
rep = this.getAMethod() and
344+
exists(Method m | this.repSCC(rep, a, m) |
345+
a.getEnclosingCallable() = m and
346+
e = a
347+
or
348+
exists(MethodCall c, Method calleeRep | this.callEdgeSCC(rep, a, c, calleeRep) | e = c)
349+
)
350+
}
351+
352+
/** Holds if all paths from `rep` to `a` are monitored by `monitor`. */
353+
predicate monitorsViaSCC(Method rep, ExposedFieldAccess a, Monitors::Monitor monitor) {
354+
rep = this.getAMethod() and
355+
this.providesAccessSCC(rep, _, a) and
356+
// If we are in an SCC that can access `a`, the access must be monitored locally
357+
(this.repSCC(rep, a, a.getEnclosingCallable()) implies Monitors::locallyMonitors(a, monitor)) and
358+
// Any call towards `a` must either be monitored or guarantee that the access is monitored
359+
forall(MethodCall c, Method calleeRep | this.callEdgeSCC(rep, a, c, calleeRep) |
273360
Monitors::locallyMonitors(c, monitor)
274361
or
275-
this.monitorsVia(c.getCallee(), a, monitor)
362+
this.monitorsViaSCC(calleeRep, a, monitor)
363+
)
364+
}
365+
366+
/** Holds if all paths from `m` to `a` are monitored by `monitor`. */
367+
predicate monitorsVia(Method m, ExposedFieldAccess a, Monitors::Monitor monitor) {
368+
exists(Method rep |
369+
this.repSCC(rep, a, m) and
370+
this.monitorsViaSCC(rep, a, monitor)
276371
)
277372
}
278373
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@
6363
| examples/LockExample.java:94:5:94:21 | notRelatedToOther | This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor. | examples/LockExample.java:94:5:94:21 | notRelatedToOther | this expression | examples/LockExample.java:103:5:103:21 | notRelatedToOther | this field access | examples/LockExample.java:103:5:103:21 | notRelatedToOther | this expression |
6464
| examples/LockExample.java:94:5:94:21 | notRelatedToOther | This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor. | examples/LockExample.java:94:5:94:21 | notRelatedToOther | this expression | examples/LockExample.java:109:5:109:21 | notRelatedToOther | this field access | examples/LockExample.java:109:5:109:21 | notRelatedToOther | this expression |
6565
| examples/LockExample.java:94:5:94:21 | notRelatedToOther | This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor. | examples/LockExample.java:94:5:94:21 | notRelatedToOther | this expression | examples/LockExample.java:117:5:117:21 | notRelatedToOther | this field access | examples/LockExample.java:117:5:117:21 | notRelatedToOther | this expression |
66-
| examples/LoopyCallGraph.java:25:5:25:9 | count | This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor. | examples/LoopyCallGraph.java:15:7:15:16 | increase(...) | this expression | examples/LoopyCallGraph.java:25:5:25:9 | count | this field access | examples/LoopyCallGraph.java:15:7:15:16 | increase(...) | this expression |
67-
| examples/LoopyCallGraph.java:25:5:25:9 | count | This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor. | examples/LoopyCallGraph.java:15:7:15:16 | increase(...) | this expression | examples/LoopyCallGraph.java:31:5:31:9 | count | this field access | examples/LoopyCallGraph.java:15:7:15:16 | increase(...) | this expression |
6866
| examples/SyncLstExample.java:40:5:40:7 | lst | This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor. | examples/SyncLstExample.java:40:5:40:7 | lst | this expression | examples/SyncLstExample.java:45:5:45:7 | lst | this field access | examples/SyncLstExample.java:45:5:45:7 | lst | this expression |
6967
| examples/SyncStackExample.java:32:5:32:7 | stc | This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor. | examples/SyncStackExample.java:32:5:32:7 | stc | this expression | examples/SyncStackExample.java:37:5:37:7 | stc | this field access | examples/SyncStackExample.java:37:5:37:7 | stc | this expression |
7068
| examples/SynchronizedAndLock.java:14:9:14:14 | length | This modifying field access (publicly accessible via $@) is conflicting with $@ (publicly accessible via $@) because they are not synchronized with the same monitor. | examples/SynchronizedAndLock.java:14:9:14:14 | length | this expression | examples/SynchronizedAndLock.java:19:9:19:14 | length | this field access | examples/SynchronizedAndLock.java:19:9:19:14 | length | this expression |

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class LoopyCallGraph {
1212

1313
public void entry() {
1414
if (random.nextBoolean()) {
15-
increase(); // this looks like an unprotected path to a call to dec()
15+
increase(); // this could look like an unprotected path to a call to dec()
1616
} else {
1717
lock.lock();
1818
dec();
@@ -22,9 +22,9 @@ public void entry() {
2222

2323
private void increase() {
2424
lock.lock();
25-
count = 10; //$ SPURIOUS: Alert
25+
count = 10;
2626
lock.unlock();
27-
entry(); // this looks like an unprotected path to a call to dec()
27+
entry(); // this could look like an unprotected path to a call to dec()
2828
}
2929

3030
private void dec() {

0 commit comments

Comments
 (0)