Skip to content

Commit c2374e5

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 aa5cc6d commit c2374e5

File tree

3 files changed

+107
-12
lines changed

3 files changed

+107
-12
lines changed

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

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -321,18 +321,115 @@ class ClassAnnotatedAsThreadSafe extends Class {
321321
)
322322
}
323323

324-
/** Holds if all paths from `m` to `a` are monitored by `monitor`. */
325-
predicate monitorsVia(Method m, ExposedFieldAccess a, Monitors::Monitor monitor) {
324+
// NOTE:
325+
// In order to deal with loops in the call graph, we compute the strongly connected components (SCCs).
326+
// We only wish to do this for the methods that can lead to exposed field accesses.
327+
// Given a field access `a`, we can consider a "call graph of interest", a sub graph of the call graph
328+
// that only contains methods that lead to an access of `a`. We call this "the call graph induced by `a`".
329+
// We can then compute the SCCs of this graph, yielding the SCC graph induced by `a`.
330+
//
331+
/**
332+
* Holds if a call to method `m` can cause an access of `a` by `m` calling `callee`.
333+
* This is an edge in the call graph induced by `a`.
334+
*/
335+
predicate accessVia(Method m, ExposedFieldAccess a, Method callee) {
336+
exists(MethodCall c | this.providesAccess(m, c, a) | callee = c.getCallee())
337+
}
338+
339+
/** Holds if `m` can reach `reached` by a path in the call graph induced by `a`. */
340+
predicate accessReach(Method m, ExposedFieldAccess a, Method reached) {
341+
m = this.getAMethod() and
342+
reached = this.getAMethod() and
343+
this.providesAccess(m, _, a) and
344+
this.providesAccess(reached, _, a) and
345+
(
346+
this.accessVia(m, a, reached)
347+
or
348+
exists(Method mid | this.accessReach(m, a, mid) | this.accessVia(mid, a, reached))
349+
)
350+
}
351+
352+
/**
353+
* Holds if `rep` is a representative of the SCC containing `m` in the call graph induced by `a`.
354+
* This only assigns representatives to methods involved in loops.
355+
* To get a representative of any method, use `repScc`.
356+
*/
357+
predicate repInLoopScc(Method rep, ExposedFieldAccess a, Method m) {
358+
// `rep` and `m` are in the same SCC
359+
this.accessReach(rep, a, m) and
360+
this.accessReach(m, a, rep) and
361+
// `rep` is the representative of the SCC
362+
// that is, the earliest in the source code
363+
forall(Method alt_rep |
364+
rep != alt_rep and
365+
this.accessReach(alt_rep, a, m) and
366+
this.accessReach(m, a, alt_rep)
367+
|
368+
rep.getLocation().getStartLine() < alt_rep.getLocation().getStartLine()
369+
)
370+
}
371+
372+
/** Holds if `rep` is a representative of the SCC containing `m` in the call graph induced by `a`. */
373+
predicate repScc(Method rep, ExposedFieldAccess a, Method m) {
374+
this.repInLoopScc(rep, a, m)
375+
or
376+
// If `m` is in the call graph induced by `a` and did not get a representative from `repInLoopScc`,
377+
// it is represented by itself.
326378
m = this.getAMethod() and
327379
this.providesAccess(m, _, a) and
328-
(a.getEnclosingCallable() = m implies Monitors::locallyMonitors(a, monitor)) and
329-
forall(MethodCall c |
330-
c.getEnclosingCallable() = m and
331-
this.providesAccess(c.getCallee(), _, a)
380+
not this.repInLoopScc(_, a, m) and
381+
rep = m
382+
}
383+
384+
/**
385+
* Holds if `c` is a call from the SCC represented by `callerRep` to the (different) SCC represented by `calleeRep`.
386+
* This is an edge in the SCC graph induced by `a`.
387+
*/
388+
predicate callEdgeScc(Method callerRep, ExposedFieldAccess a, MethodCall c, Method calleeRep) {
389+
callerRep != calleeRep and
390+
exists(Method caller, Method callee |
391+
this.repScc(callerRep, a, caller) and
392+
this.repScc(calleeRep, a, callee)
332393
|
394+
this.accessVia(caller, a, callee) and
395+
c.getEnclosingCallable() = caller and
396+
c.getCallee() = callee
397+
)
398+
}
399+
400+
/**
401+
* Holds if the SCC represented by `rep` can cause an access to `a` and `e` is the expression that leads to that access.
402+
* `e` will either be `a` itself or a method call that leads to `a` via a different SCC.
403+
*/
404+
predicate providesAccessScc(Method rep, Expr e, ExposedFieldAccess a) {
405+
rep = this.getAMethod() and
406+
exists(Method m | this.repScc(rep, a, m) |
407+
a.getEnclosingCallable() = m and
408+
e = a
409+
or
410+
exists(MethodCall c | this.callEdgeScc(rep, a, c, _) | e = c)
411+
)
412+
}
413+
414+
/** Holds if all paths from `rep` to `a` are monitored by `monitor`. */
415+
predicate monitorsViaScc(Method rep, ExposedFieldAccess a, Monitors::Monitor monitor) {
416+
rep = this.getAMethod() and
417+
this.providesAccessScc(rep, _, a) and
418+
// If we are in an SCC that can access `a`, the access must be monitored locally
419+
(this.repScc(rep, a, a.getEnclosingCallable()) implies Monitors::locallyMonitors(a, monitor)) and
420+
// Any call towards `a` must either be monitored or guarantee that the access is monitored
421+
forall(MethodCall c, Method calleeRep | this.callEdgeScc(rep, a, c, calleeRep) |
333422
Monitors::locallyMonitors(c, monitor)
334423
or
335-
this.monitorsVia(c.getCallee(), a, monitor)
424+
this.monitorsViaScc(calleeRep, a, monitor)
425+
)
426+
}
427+
428+
/** Holds if all paths from `m` to `a` are monitored by `monitor`. */
429+
predicate monitorsVia(Method m, ExposedFieldAccess a, Monitors::Monitor monitor) {
430+
exists(Method rep |
431+
this.repScc(rep, a, m) and
432+
this.monitorsViaScc(rep, a, monitor)
336433
)
337434
}
338435
}

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)