Skip to content

Commit 2c9a6e7

Browse files
committed
JS: Cache function-wrapping steps in type-tracking stage
1 parent 43ca8ea commit 2c9a6e7

File tree

3 files changed

+147
-124
lines changed

3 files changed

+147
-124
lines changed

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

Lines changed: 1 addition & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,130 +1682,7 @@ module DataFlow {
16821682
import Configuration
16831683
import TrackedNodes
16841684
import TypeTracking
1685+
import internal.FunctionWrapperSteps
16851686

16861687
predicate localTaintStep = TaintTracking::localTaintStep/2;
1687-
1688-
private predicate forwardsParameter(
1689-
DataFlow::FunctionNode function, int i, DataFlow::CallNode call
1690-
) {
1691-
exists(DataFlow::ParameterNode parameter | parameter = function.getParameter(i) |
1692-
not parameter.isRestParameter() and
1693-
parameter.flowsTo(call.getArgument(i))
1694-
or
1695-
parameter.isRestParameter() and
1696-
parameter.flowsTo(call.getASpreadArgument())
1697-
)
1698-
}
1699-
1700-
/**
1701-
* Holds if the function in `succ` forwards all its arguments to a call to `pred` and returns
1702-
* its result. This can thus be seen as a step `pred -> succ` used for tracking function values
1703-
* through "wrapper functions", since the `succ` function partially replicates behavior of `pred`.
1704-
*
1705-
* Examples:
1706-
* ```js
1707-
* function f(x) {
1708-
* return g(x); // step: g -> f
1709-
* }
1710-
*
1711-
* function doExec(x) {
1712-
* console.log(x);
1713-
* return exec(x); // step: exec -> doExec
1714-
* }
1715-
*
1716-
* function doEither(x, y) {
1717-
* if (x > y) {
1718-
* return foo(x, y); // step: foo -> doEither
1719-
* } else {
1720-
* return bar(x, y); // step: bar -> doEither
1721-
* }
1722-
* }
1723-
*
1724-
* function wrapWithLogging(f) {
1725-
* return (x) => {
1726-
* console.log(x);
1727-
* return f(x); // step: f -> anonymous function
1728-
* }
1729-
* }
1730-
* wrapWithLogging(g); // step: g -> wrapWithLogging(g)
1731-
* ```
1732-
*/
1733-
predicate functionForwardingStep(DataFlow::Node pred, DataFlow::Node succ) {
1734-
exists(DataFlow::FunctionNode function, DataFlow::CallNode call |
1735-
call.flowsTo(function.getReturnNode()) and
1736-
forall(int i | exists([call.getArgument(i), function.getParameter(i)]) |
1737-
forwardsParameter(function, i, call)
1738-
) and
1739-
pred = call.getCalleeNode() and
1740-
succ = function
1741-
)
1742-
or
1743-
// Given a generic wrapper function like,
1744-
//
1745-
// function wrap(f) { return (x, y) => f(x, y) };
1746-
//
1747-
// add steps through calls to that function: `g -> wrap(g)`
1748-
exists(DataFlow::FunctionNode wrapperFunction, SourceNode param, Node paramUse |
1749-
FlowSteps::argumentPassing(succ, pred, wrapperFunction.getFunction(), param) and
1750-
param.flowsTo(paramUse) and
1751-
functionForwardingStep(paramUse, wrapperFunction.getReturnNode().getALocalSource())
1752-
)
1753-
}
1754-
1755-
/**
1756-
* Holds if the function in `succ` forwards all its arguments to a call to `pred`.
1757-
* This can thus be seen as a step `pred -> succ` used for tracking function values
1758-
* through "wrapper functions", since the `succ` function partially replicates behavior of `pred`.
1759-
*
1760-
* This is similar to `functionForwardingStep` except the innermost forwarding call does not
1761-
* need flow to the return value; this can be useful for tracking callback-style functions
1762-
* where the result tends to be unused.
1763-
*
1764-
* Examples:
1765-
* ```js
1766-
* function f(x, callback) {
1767-
* g(x, callback); // step: g -> f
1768-
* }
1769-
*
1770-
* function doExec(x, callback) {
1771-
* console.log(x);
1772-
* exec(x, callback); // step: exec -> doExec
1773-
* }
1774-
*
1775-
* function doEither(x, y) {
1776-
* if (x > y) {
1777-
* return foo(x, y); // step: foo -> doEither
1778-
* } else {
1779-
* return bar(x, y); // step: bar -> doEither
1780-
* }
1781-
* }
1782-
*
1783-
* function wrapWithLogging(f) {
1784-
* return (x) => {
1785-
* console.log(x);
1786-
* return f(x); // step: f -> anonymous function
1787-
* }
1788-
* }
1789-
* wrapWithLogging(g); // step: g -> wrapWithLogging(g)
1790-
* ```
1791-
*/
1792-
predicate functionOneWayForwardingStep(DataFlow::Node pred, DataFlow::Node succ) {
1793-
exists(DataFlow::FunctionNode function, DataFlow::CallNode call |
1794-
call.getContainer() = function.getFunction() and
1795-
forall(int i | exists(function.getParameter(i)) | forwardsParameter(function, i, call)) and
1796-
pred = call.getCalleeNode() and
1797-
succ = function
1798-
)
1799-
or
1800-
// Given a generic wrapper function like,
1801-
//
1802-
// function wrap(f) { return (x, y) => f(x, y) };
1803-
//
1804-
// add steps through calls to that function: `g -> wrap(g)`
1805-
exists(DataFlow::FunctionNode wrapperFunction, SourceNode param, Node paramUse |
1806-
FlowSteps::argumentPassing(succ, pred, wrapperFunction.getFunction(), param) and
1807-
param.flowsTo(paramUse) and
1808-
functionOneWayForwardingStep(paramUse, wrapperFunction.getReturnNode().getALocalSource())
1809-
)
1810-
}
18111688
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
private import javascript
2+
3+
private import FlowSteps as FlowSteps
4+
private import semmle.javascript.internal.CachedStages
5+
6+
cached
7+
private module Cached {
8+
private predicate forwardsParameter(
9+
DataFlow::FunctionNode function, int i, DataFlow::CallNode call
10+
) {
11+
exists(DataFlow::ParameterNode parameter | parameter = function.getParameter(i) |
12+
not parameter.isRestParameter() and
13+
parameter.flowsTo(call.getArgument(i))
14+
or
15+
parameter.isRestParameter() and
16+
parameter.flowsTo(call.getASpreadArgument())
17+
)
18+
}
19+
20+
cached
21+
private module Stage {
22+
// Forces the module to be computed as part of the type-tracking stage.
23+
cached
24+
predicate forceStage() {
25+
Stages::TypeTracking::ref()
26+
}
27+
}
28+
29+
/**
30+
* Holds if the function in `succ` forwards all its arguments to a call to `pred` and returns
31+
* its result. This can thus be seen as a step `pred -> succ` used for tracking function values
32+
* through "wrapper functions", since the `succ` function partially replicates behavior of `pred`.
33+
*
34+
* Examples:
35+
* ```js
36+
* function f(x) {
37+
* return g(x); // step: g -> f
38+
* }
39+
*
40+
* function doExec(x) {
41+
* console.log(x);
42+
* return exec(x); // step: exec -> doExec
43+
* }
44+
*
45+
* function doEither(x, y) {
46+
* if (x > y) {
47+
* return foo(x, y); // step: foo -> doEither
48+
* } else {
49+
* return bar(x, y); // step: bar -> doEither
50+
* }
51+
* }
52+
*
53+
* function wrapWithLogging(f) {
54+
* return (x) => {
55+
* console.log(x);
56+
* return f(x); // step: f -> anonymous function
57+
* }
58+
* }
59+
* wrapWithLogging(g); // step: g -> wrapWithLogging(g)
60+
* ```
61+
*/
62+
cached
63+
predicate functionForwardingStep(DataFlow::Node pred, DataFlow::Node succ) {
64+
exists(DataFlow::FunctionNode function, DataFlow::CallNode call |
65+
call.flowsTo(function.getReturnNode()) and
66+
forall(int i | exists([call.getArgument(i), function.getParameter(i)]) |
67+
forwardsParameter(function, i, call)
68+
) and
69+
pred = call.getCalleeNode() and
70+
succ = function
71+
)
72+
or
73+
// Given a generic wrapper function like,
74+
//
75+
// function wrap(f) { return (x, y) => f(x, y) };
76+
//
77+
// add steps through calls to that function: `g -> wrap(g)`
78+
exists(DataFlow::FunctionNode wrapperFunction, DataFlow::SourceNode param, DataFlow::Node paramUse |
79+
FlowSteps::argumentPassing(succ, pred, wrapperFunction.getFunction(), param) and
80+
param.flowsTo(paramUse) and
81+
functionForwardingStep(paramUse, wrapperFunction.getReturnNode().getALocalSource())
82+
)
83+
}
84+
85+
/**
86+
* Holds if the function in `succ` forwards all its arguments to a call to `pred`.
87+
* This can thus be seen as a step `pred -> succ` used for tracking function values
88+
* through "wrapper functions", since the `succ` function partially replicates behavior of `pred`.
89+
*
90+
* This is similar to `functionForwardingStep` except the innermost forwarding call does not
91+
* need flow to the return value; this can be useful for tracking callback-style functions
92+
* where the result tends to be unused.
93+
*
94+
* Examples:
95+
* ```js
96+
* function f(x, callback) {
97+
* g(x, callback); // step: g -> f
98+
* }
99+
*
100+
* function doExec(x, callback) {
101+
* console.log(x);
102+
* exec(x, callback); // step: exec -> doExec
103+
* }
104+
*
105+
* function doEither(x, y) {
106+
* if (x > y) {
107+
* return foo(x, y); // step: foo -> doEither
108+
* } else {
109+
* return bar(x, y); // step: bar -> doEither
110+
* }
111+
* }
112+
*
113+
* function wrapWithLogging(f) {
114+
* return (x) => {
115+
* console.log(x);
116+
* return f(x); // step: f -> anonymous function
117+
* }
118+
* }
119+
* wrapWithLogging(g); // step: g -> wrapWithLogging(g)
120+
* ```
121+
*/
122+
cached
123+
predicate functionOneWayForwardingStep(DataFlow::Node pred, DataFlow::Node succ) {
124+
exists(DataFlow::FunctionNode function, DataFlow::CallNode call |
125+
call.getContainer() = function.getFunction() and
126+
forall(int i | exists(function.getParameter(i)) | forwardsParameter(function, i, call)) and
127+
pred = call.getCalleeNode() and
128+
succ = function
129+
)
130+
or
131+
// Given a generic wrapper function like,
132+
//
133+
// function wrap(f) { return (x, y) => f(x, y) };
134+
//
135+
// add steps through calls to that function: `g -> wrap(g)`
136+
exists(DataFlow::FunctionNode wrapperFunction, DataFlow::SourceNode param, DataFlow::Node paramUse |
137+
FlowSteps::argumentPassing(succ, pred, wrapperFunction.getFunction(), param) and
138+
param.flowsTo(paramUse) and
139+
functionOneWayForwardingStep(paramUse, wrapperFunction.getReturnNode().getALocalSource())
140+
)
141+
}
142+
}
143+
144+
import Cached

javascript/ql/src/semmle/javascript/internal/CachedStages.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ module Stages {
196196
exists(any(DataFlow::TypeTracker t).append(_))
197197
or
198198
exists(any(DataFlow::TypeBackTracker t).prepend(_))
199+
or
200+
DataFlow::functionForwardingStep(_, _)
199201
}
200202
}
201203

0 commit comments

Comments
 (0)