Skip to content

Commit c017308

Browse files
authored
Merge pull request #4134 from erik-krogh/genCalls
Approved by asgerf
2 parents 5760213 + 6cbdc7a commit c017308

File tree

10 files changed

+113
-5
lines changed

10 files changed

+113
-5
lines changed

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import semmle.javascript.Extend
2727
import semmle.javascript.Externs
2828
import semmle.javascript.Files
2929
import semmle.javascript.Functions
30+
import semmle.javascript.Generators
3031
import semmle.javascript.GlobalAccessPaths
3132
import semmle.javascript.HTML
3233
import semmle.javascript.HtmlSanitizers

javascript/ql/src/semmle/javascript/Functions.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
204204
/** Holds if this function is an asynchronous function. */
205205
predicate isAsync() { isAsync(this) }
206206

207+
/** Holds if this function is asynchronous or a generator. */
208+
predicate isAsyncOrGenerator() { isAsync() or isGenerator() }
209+
207210
/** Gets the enclosing function or toplevel of this function. */
208211
override StmtContainer getEnclosingContainer() { result = getEnclosingStmt().getContainer() }
209212

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Provides classes and predicates for working with generator functions.
3+
*/
4+
5+
import javascript
6+
private import semmle.javascript.dataflow.internal.PreCallGraphStep
7+
8+
/**
9+
* Classes and predicates for modelling data-flow for generator functions.
10+
*/
11+
private module GeneratorDataFlow {
12+
private import DataFlow::PseudoProperties
13+
14+
private class ArrayIteration extends PreCallGraphStep {
15+
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
16+
exists(DataFlow::FunctionNode f | f.getFunction().isGenerator() |
17+
prop = iteratorElement() and
18+
exists(YieldExpr yield |
19+
yield.getContainer() = f.getFunction() and not yield.isDelegating()
20+
|
21+
pred.asExpr() = yield.getOperand()
22+
) and
23+
succ = f.getReturnNode()
24+
)
25+
}
26+
27+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
28+
exists(DataFlow::FunctionNode f | f.getFunction().isGenerator() |
29+
prop = iteratorElement() and
30+
exists(YieldExpr yield | yield.getContainer() = f.getFunction() and yield.isDelegating() |
31+
pred.asExpr() = yield.getOperand()
32+
) and
33+
succ = f.getReturnNode()
34+
)
35+
}
36+
}
37+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,7 @@ private predicate storeStep(
10311031
summary = PathSummary::level()
10321032
or
10331033
exists(Function f, DataFlow::Node mid, DataFlow::Node invk |
1034-
not f.isAsync() and invk = succ
1034+
not f.isAsyncOrGenerator() and invk = succ
10351035
or
10361036
// store in an immediately awaited function call
10371037
f.isAsync() and
@@ -1156,7 +1156,7 @@ private predicate loadStep(
11561156
summary = PathSummary::level()
11571157
or
11581158
exists(Function f, DataFlow::Node read, DataFlow::Node invk |
1159-
not f.isAsync() and invk = succ
1159+
not f.isAsyncOrGenerator() and invk = succ
11601160
or
11611161
// load from an immediately awaited function call
11621162
f.isAsync() and

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1495,7 +1495,7 @@ module DataFlow {
14951495
)
14961496
or
14971497
// from returned expr to the FunctionReturnNode.
1498-
exists(Function f | not f.isAsync() |
1498+
exists(Function f | not f.isAsyncOrGenerator() |
14991499
DataFlow::functionReturnNode(succ, f) and pred = valueNode(f.getAReturnedExpr())
15001500
)
15011501
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ private module NodeTracking {
243243
basicStoreStep(pred, succ, prop) and
244244
summary = PathSummary::level()
245245
or
246-
exists(Function f, DataFlow::Node mid | not f.isAsync() |
246+
exists(Function f, DataFlow::Node mid | not f.isAsyncOrGenerator() |
247247
// `f` stores its parameter `pred` in property `prop` of a value that flows back to the caller,
248248
// and `succ` is an invocation of `f`
249249
reachableFromInput(f, succ, pred, mid, summary) and
@@ -266,7 +266,7 @@ private module NodeTracking {
266266
basicLoadStep(pred, succ, prop) and
267267
summary = PathSummary::level()
268268
or
269-
exists(Function f, DataFlow::SourceNode parm | not f.isAsync() |
269+
exists(Function f, DataFlow::SourceNode parm | not f.isAsyncOrGenerator() |
270270
argumentPassing(succ, pred, f, parm) and
271271
reachesReturn(f, parm.getAPropertyRead(prop), summary)
272272
)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ predicate localExceptionStep(DataFlow::Node pred, DataFlow::Node succ) {
7171
*/
7272
predicate localExceptionStepWithAsyncFlag(DataFlow::Node pred, DataFlow::Node succ, boolean async) {
7373
exists(DataFlow::Node target | target = getThrowTarget(pred) |
74+
// this also covers generators - as the behavior of exceptions is close enough to the behavior of ordinary
75+
// functions when it comes to exceptions (assuming that the iterator does not cross function boundaries).
7476
async = false and
7577
succ = target and
7678
not succ = any(DataFlow::FunctionNode f | f.getFunction().isAsync()).getExceptionalReturn()

javascript/ql/test/library-tests/Generators/DataFlow.expected

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import javascript
2+
import testUtilities.ConsistencyChecking
3+
4+
class GeneratorFlowConfig extends DataFlow::Configuration {
5+
GeneratorFlowConfig() { this = "GeneratorFlowConfig" }
6+
7+
override predicate isSource(DataFlow::Node source) { source.asExpr().getStringValue() = "source" }
8+
9+
override predicate isSink(DataFlow::Node sink) {
10+
sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument()
11+
}
12+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
(function () {
2+
var source = "source";
3+
sink(source); // NOT OK
4+
5+
function *gen1() {
6+
yield source;
7+
}
8+
for (const x of gen1()) {
9+
sink(x); // NOT OK
10+
}
11+
12+
function *gen2() {
13+
yield "safe";
14+
return source;
15+
}
16+
sink(gen2()); // OK
17+
18+
Array.from(gen1()).forEach(x => sink(x)); // NOT OK
19+
20+
function gen3() {
21+
yield source;
22+
}
23+
Array.from(gen3()).forEach(x => sink(x)); // NOT OK
24+
25+
function *gen4() {
26+
throw source;
27+
}
28+
try {
29+
Array.from(gen4());
30+
} catch (e) {
31+
sink(e); // NOT OK
32+
}
33+
34+
function *delegating() {
35+
yield* delegate();
36+
}
37+
38+
function *delegate() {
39+
yield source;
40+
}
41+
42+
Array.from(delegating()).forEach(x => sink(x)); // NOT OK
43+
44+
function *delegating2() {
45+
yield* returnsTaint();
46+
}
47+
48+
function returnsTaint() {
49+
return source;
50+
}
51+
52+
Array.from(delegating2()).forEach(x => sink(x)); // OK
53+
});

0 commit comments

Comments
 (0)