Skip to content

Commit d32cd70

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 05436a3 commit d32cd70

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
@@ -261,18 +261,115 @@ 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
316+
// If `m` is in the call graph induced by `a` and did not get a representative from `repInLoopScc`,
317+
// it is represented by itself.
266318
m = this.getAMethod() and
267319
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)
320+
not this.repInLoopScc(_, a, m) and
321+
rep = m
322+
}
323+
324+
/**
325+
* Holds if `c` is a call from the SCC represented by `callerRep` to the (different) SCC represented by `calleeRep`.
326+
* This is an edge in the SCC graph induced by `a`.
327+
*/
328+
predicate callEdgeScc(Method callerRep, ExposedFieldAccess a, MethodCall c, Method calleeRep) {
329+
callerRep != calleeRep and
330+
exists(Method caller, Method callee |
331+
this.repScc(callerRep, a, caller) and
332+
this.repScc(calleeRep, a, callee)
272333
|
334+
this.accessVia(caller, a, callee) and
335+
c.getEnclosingCallable() = caller and
336+
c.getCallee() = callee
337+
)
338+
}
339+
340+
/**
341+
* Holds if the SCC represented by `rep` can cause an access to `a` and `e` is the expression that leads to that access.
342+
* `e` will either be `a` itself or a method call that leads to `a` via a different SCC.
343+
*/
344+
predicate providesAccessScc(Method rep, Expr e, ExposedFieldAccess a) {
345+
rep = this.getAMethod() and
346+
exists(Method m | this.repScc(rep, a, m) |
347+
a.getEnclosingCallable() = m and
348+
e = a
349+
or
350+
exists(MethodCall c | this.callEdgeScc(rep, a, c, _) | e = c)
351+
)
352+
}
353+
354+
/** Holds if all paths from `rep` to `a` are monitored by `monitor`. */
355+
predicate monitorsViaScc(Method rep, ExposedFieldAccess a, Monitors::Monitor monitor) {
356+
rep = this.getAMethod() and
357+
this.providesAccessScc(rep, _, a) and
358+
// If we are in an SCC that can access `a`, the access must be monitored locally
359+
(this.repScc(rep, a, a.getEnclosingCallable()) implies Monitors::locallyMonitors(a, monitor)) and
360+
// Any call towards `a` must either be monitored or guarantee that the access is monitored
361+
forall(MethodCall c, Method calleeRep | this.callEdgeScc(rep, a, c, calleeRep) |
273362
Monitors::locallyMonitors(c, monitor)
274363
or
275-
this.monitorsVia(c.getCallee(), a, monitor)
364+
this.monitorsViaScc(calleeRep, a, monitor)
365+
)
366+
}
367+
368+
/** Holds if all paths from `m` to `a` are monitored by `monitor`. */
369+
predicate monitorsVia(Method m, ExposedFieldAccess a, Monitors::Monitor monitor) {
370+
exists(Method rep |
371+
this.repScc(rep, a, m) and
372+
this.monitorsViaScc(rep, a, monitor)
276373
)
277374
}
278375
}

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)