Skip to content

Commit 08d852f

Browse files
Merge pull request #1048 from jbj/dataflow-link-targets
C++: Data flow dispatch across link targets
2 parents e83dd67 + 80b0765 commit 08d852f

File tree

5 files changed

+95
-74
lines changed

5 files changed

+95
-74
lines changed

cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowDispatch.qll

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,59 @@ private import cpp
22
private import DataFlowPrivate
33

44
Function viableImpl(MethodAccess ma) {
5-
result = ma.getTarget()
5+
result = viableCallable(ma)
66
}
77

8+
/**
9+
* Gets a function that might be called by `call`.
10+
*/
811
Function viableCallable(Call call) {
912
result = call.getTarget()
13+
or
14+
// If the target of the call does not have a body in the snapshot, it might
15+
// be because the target is just a header declaration, and the real target
16+
// will be determined at run time when the caller and callee are linked
17+
// together by the operating system's dynamic linker. In case a _unique_
18+
// function with the right signature is present in the database, we return
19+
// that as a potential callee.
20+
exists(string qualifiedName, int nparams |
21+
callSignatureWithoutBody(qualifiedName, nparams, call) and
22+
functionSignatureWithBody(qualifiedName, nparams, result) and
23+
strictcount(Function other | functionSignatureWithBody(qualifiedName, nparams, other)) = 1
24+
)
25+
}
26+
27+
/**
28+
* Holds if `f` is a function with a body that has name `qualifiedName` and
29+
* `nparams` parameter count. See `functionSignature`.
30+
*/
31+
private predicate functionSignatureWithBody(string qualifiedName, int nparams, Function f) {
32+
functionSignature(f, qualifiedName, nparams) and
33+
exists(f.getBlock())
34+
}
35+
36+
/**
37+
* Holds if the target of `call` is a function _with no definition_ that has
38+
* name `qualifiedName` and `nparams` parameter count. See `functionSignature`.
39+
*/
40+
pragma[noinline]
41+
private predicate callSignatureWithoutBody(string qualifiedName, int nparams, Call call) {
42+
exists(Function target |
43+
target = call.getTarget() and
44+
not exists(target.getBlock()) and
45+
functionSignature(target, qualifiedName, nparams)
46+
)
47+
}
48+
49+
/**
50+
* Holds if `f` has name `qualifiedName` and `nparams` parameter count. This is
51+
* an approximation of its signature for the purpose of matching functions that
52+
* might be the same across link targets.
53+
*/
54+
private predicate functionSignature(Function f, string qualifiedName, int nparams) {
55+
qualifiedName = f.getQualifiedName() and
56+
nparams = f.getNumberOfParameters() and
57+
not f.isStatic()
1058
}
1159

1260
/**
Lines changed: 1 addition & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1 @@
1-
private import cpp
2-
private import DataFlowPrivate
3-
4-
Function viableImpl(MethodAccess ma) {
5-
result = ma.getTarget()
6-
}
7-
8-
Function viableCallable(Call call) {
9-
result = call.getTarget()
10-
}
11-
12-
/**
13-
* Holds if the call context `ctx` reduces the set of viable dispatch
14-
* targets of `ma` in `c`.
15-
*/
16-
predicate reducedViableImplInCallContext(MethodAccess ma, Callable c, Call ctx) {
17-
none()
18-
}
19-
20-
/**
21-
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
22-
* restricted to those `ma`s for which a context might make a difference.
23-
*/
24-
private Method viableImplInCallContext(MethodAccess ma, Call ctx) {
25-
// stub implementation
26-
result = viableImpl(ma) and
27-
viableCallable(ctx) = ma.getEnclosingFunction()
28-
}
29-
30-
/**
31-
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
32-
* restricted to those `ma`s for which the context makes a difference.
33-
*/
34-
Method prunedViableImplInCallContext(MethodAccess ma, Call ctx) {
35-
result = viableImplInCallContext(ma, ctx) and
36-
reducedViableImplInCallContext(ma, _, ctx)
37-
}
38-
39-
/**
40-
* Holds if data might flow from `ma` to a return statement in some
41-
* configuration.
42-
*/
43-
private predicate maybeChainedReturn(MethodAccess ma) {
44-
exists(ReturnStmt ret |
45-
exists(ret.getExpr()) and
46-
ret.getEnclosingFunction() = ma.getEnclosingFunction() and
47-
not ma.getParent() instanceof ExprStmt
48-
)
49-
}
50-
51-
/**
52-
* Holds if flow returning from `m` to `ma` might return further and if
53-
* this path restricts the set of call sites that can be returned to.
54-
*/
55-
predicate reducedViableImplInReturn(Method m, MethodAccess ma) {
56-
exists(int tgts, int ctxtgts |
57-
m = viableImpl(ma) and
58-
ctxtgts = count(Call ctx | m = viableImplInCallContext(ma, ctx)) and
59-
tgts = strictcount(Call ctx | viableCallable(ctx) = ma.getEnclosingFunction()) and
60-
ctxtgts < tgts
61-
) and
62-
maybeChainedReturn(ma)
63-
}
64-
65-
/**
66-
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
67-
* restricted to those `ma`s and results for which the return flow from the
68-
* result to `ma` restricts the possible context `ctx`.
69-
*/
70-
Method prunedViableImplInCallContextReverse(MethodAccess ma, Call ctx) {
71-
result = viableImplInCallContext(ma, ctx) and
72-
reducedViableImplInReturn(result, ma)
73-
}
1+
import semmle.code.cpp.dataflow.internal.DataFlowDispatch
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
int source();
2+
void sink(int);
3+
4+
// For the purpose of this test, this function acts like it's defined in a
5+
// different link target such that we get two different functions named
6+
// `calleeAcrossLinkTargets` and no link from the caller to this one. The test
7+
// is getting that effect because the library can't distinguish the two
8+
// overloaded functions that differ only on `int` vs. `long`, so we might lose
9+
// this result and be forced to write a better test if the function signature
10+
// detection should improve.
11+
void calleeAcrossLinkTargets(long x) {
12+
sink(x);
13+
}
14+
15+
void calleeAcrossLinkTargets(int x); // no body
16+
17+
18+
void callerAcrossLinkTargets() {
19+
calleeAcrossLinkTargets(source());
20+
}
21+
22+
///////////////////////////////////////////////////////////////////////////////
23+
24+
25+
// No flow into this function as its signature is not unique (in the limited
26+
// model of the library).
27+
void ambiguousCallee(long x) {
28+
sink(x);
29+
}
30+
31+
// No flow into this function as its signature is not unique (in the limited
32+
// model of the library).
33+
void ambiguousCallee(short x) {
34+
sink(x);
35+
}
36+
37+
void ambiguousCallee(int x); // no body
38+
39+
40+
void ambiguousCaller() {
41+
ambiguousCallee(source());
42+
}

cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
| acrossLinkTargets.cpp:12:8:12:8 | x | acrossLinkTargets.cpp:19:27:19:32 | call to source |
12
| test.cpp:7:8:7:9 | t1 | test.cpp:6:12:6:17 | call to source |
23
| test.cpp:9:8:9:9 | t1 | test.cpp:6:12:6:17 | call to source |
34
| test.cpp:10:8:10:9 | t2 | test.cpp:6:12:6:17 | call to source |

cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
| acrossLinkTargets.cpp:12:8:12:8 | Convert: (int)... | acrossLinkTargets.cpp:19:27:19:32 | Call: call to source |
2+
| acrossLinkTargets.cpp:12:8:12:8 | Load: x | acrossLinkTargets.cpp:19:27:19:32 | Call: call to source |
13
| test.cpp:7:8:7:9 | Load: t1 | test.cpp:6:12:6:17 | Call: call to source |
24
| test.cpp:9:8:9:9 | Load: t1 | test.cpp:6:12:6:17 | Call: call to source |
35
| test.cpp:10:8:10:9 | Load: t2 | test.cpp:6:12:6:17 | Call: call to source |

0 commit comments

Comments
 (0)