Skip to content

Commit 8c36b99

Browse files
committed
JS: Track flow into calls to bound functions
1 parent ee5cf95 commit 8c36b99

6 files changed

Lines changed: 90 additions & 13 deletions

File tree

javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ module DataFlow {
147147
final FunctionNode getABoundFunctionValue(int boundArgs) {
148148
result = getAFunctionValue() and boundArgs = 0
149149
or
150-
CallGraph::getABoundFunctionReference(result, boundArgs).flowsTo(this)
150+
CallGraph::getABoundFunctionReference(result, boundArgs, _).flowsTo(this)
151151
}
152152

153153
/**

javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ module CallGraph {
7474
*/
7575
pragma[nomagic]
7676
private
77-
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) {
77+
DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) {
7878
exists(DataFlow::PartialInvokeNode partial, DataFlow::Node callback |
7979
result = partial.getBoundFunction(callback, boundArgs) and
8080
getAFunctionReference(function, 0, t.continue()).flowsTo(callback)
@@ -90,7 +90,7 @@ module CallGraph {
9090
private
9191
DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, DataFlow::StepSummary summary) {
9292
exists(DataFlow::SourceNode prev |
93-
prev = getABoundFunctionReference(function, boundArgs, t) and
93+
prev = getABoundFunctionReferenceAux(function, boundArgs, t) and
9494
DataFlow::StepSummary::step(prev, result, summary)
9595
)
9696
}
@@ -100,8 +100,12 @@ module CallGraph {
100100
* with `function` as the underlying function.
101101
*/
102102
cached
103-
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs) {
104-
result = getABoundFunctionReference(function, boundArgs, DataFlow::TypeTracker::end())
103+
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, boolean contextDependent) {
104+
exists(DataFlow::TypeTracker t |
105+
result = getABoundFunctionReferenceAux(function, boundArgs, t) and
106+
t.end() and
107+
contextDependent = t.hasCall()
108+
)
105109
}
106110

107111
/**

javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import javascript
88
import semmle.javascript.dataflow.Configuration
9+
import semmle.javascript.dataflow.internal.CallGraphs
910

1011
/**
1112
* Holds if flow should be tracked through properties of `obj`.
@@ -91,6 +92,18 @@ private module CachedSteps {
9192
cached
9293
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
9394

95+
/**
96+
* Holds if `invk` may invoke a bound version of `f` with `boundArgs` already bound.
97+
*
98+
* The receiver is assumed to be bound as well, and should not propagate into `f`.
99+
*
100+
* Does not hold for context-dependent call sites, such as callback invocations.
101+
*/
102+
cached
103+
predicate callsBound(DataFlow::InvokeNode invk, Function f, int boundArgs) {
104+
CallGraph::getABoundFunctionReference(f.flow(), boundArgs, false).flowsTo(invk.getCalleeNode())
105+
}
106+
94107
/**
95108
* Holds if `invk` may invoke `f` indirectly through the given `callback` argument.
96109
*
@@ -141,6 +154,14 @@ private module CachedSteps {
141154
partiallyCalls(invk, callback, f) and
142155
parm = DataFlow::thisNode(f)
143156
)
157+
or
158+
exists(int boundArgs, int i, Parameter p |
159+
callsBound(invk, f, boundArgs) and
160+
f.getParameter(boundArgs + i) = p and
161+
not p.isRestParameter() and
162+
arg = invk.getArgument(i) and
163+
parm = DataFlow::parameterNode(p)
164+
)
144165
}
145166

146167
/**
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
spuriousCallee
22
missingCallee
3-
| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} |
4-
| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} |
3+
| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 |
4+
| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 |
55
badAnnotation

javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,37 @@ class AnnotatedCall extends InvokeExpr {
3333
string getCallTargetName() { result = calls }
3434

3535
AnnotatedFunction getAnExpectedCallee() { result.getCalleeName() = getCallTargetName() }
36+
37+
int getBoundArgs() {
38+
result = getAnnotation(this, "boundArgs").toInt()
39+
}
40+
41+
int getBoundArgsOrMinusOne() {
42+
result = getBoundArgs()
43+
or
44+
not exists(getBoundArgs()) and
45+
result = -1
46+
}
47+
}
48+
49+
predicate callEdge(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
50+
FlowSteps::calls(call.flow(), target) and boundArgs = -1
51+
or
52+
FlowSteps::callsBound(call.flow(), target, boundArgs)
3653
}
3754

38-
query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target) {
39-
FlowSteps::calls(call.flow(), target) and
40-
not target = call.getAnExpectedCallee()
55+
query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
56+
callEdge(call, target, boundArgs) and
57+
not (
58+
target = call.getAnExpectedCallee() and
59+
boundArgs = call.getBoundArgsOrMinusOne()
60+
)
4161
}
4262

43-
query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target) {
44-
not FlowSteps::calls(call.flow(), target) and
45-
target = call.getAnExpectedCallee()
63+
query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
64+
not callEdge(call, target, boundArgs) and
65+
target = call.getAnExpectedCallee() and
66+
boundArgs = call.getBoundArgsOrMinusOne()
4667
}
4768

4869
query predicate badAnnotation(string name) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
var url = require('url')
2+
var http = require('http')
3+
4+
function Mount () {}
5+
6+
/** name:mount.serve */
7+
Mount.prototype.serve = function (x) {
8+
}
9+
10+
function makeMount() {
11+
var m = new Mount()
12+
return m.serve.bind(m);
13+
}
14+
15+
function makeMount2(x) {
16+
var m = new Mount()
17+
return m.serve.bind(m, x);
18+
}
19+
20+
var mount = makeMount()
21+
var mount2 = makeMount2(null);
22+
23+
http.createServer(function (req, res) {
24+
/** calls:mount.serve */
25+
/** boundArgs:0 */
26+
mount(req, res)
27+
28+
/** calls:mount.serve */
29+
/** boundArgs:1 */
30+
mount2(res)
31+
});

0 commit comments

Comments
 (0)