Skip to content

Commit 126f974

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 d94bc33 commit 126f974

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
@@ -320,18 +320,115 @@ class ClassAnnotatedAsThreadSafe extends Class {
320320
)
321321
}
322322

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

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)