Skip to content

Commit 9fc5c0b

Browse files
committed
JS: Update ComposedFunctions
1 parent 7345df6 commit 9fc5c0b

File tree

3 files changed

+138
-55
lines changed

3 files changed

+138
-55
lines changed

javascript/ql/src/semmle/javascript/frameworks/ComposedFunctions.qll

Lines changed: 108 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,110 @@
55
import javascript
66

77
/**
8-
* A function composed from a collection of functions.
8+
* A call to a function that constructs a function composition `f(g(h(...)))` from a
9+
* series functions `f, g, h, ...`.
910
*/
10-
private class ComposedFunction extends DataFlow::CallNode {
11-
ComposedFunction() {
12-
exists(string name |
13-
name = "just-compose" or
14-
name = "compose-function"
15-
|
16-
this = DataFlow::moduleImport(name).getACall()
17-
)
18-
or
19-
this = LodashUnderscore::member("flow").getACall()
11+
class FunctionCompositionCall extends DataFlow::CallNode {
12+
FunctionCompositionCall::Range range;
13+
14+
FunctionCompositionCall() { this = range }
15+
16+
/**
17+
* Gets the `i`th function in the composition `f(g(h(...)))`, counting from left to right.
18+
*
19+
* Note that this is the opposite of the order in which the function are invoked,
20+
* that is, `g` occurs later than `f` in `f(g(...))` but is invoked before `f`.
21+
*/
22+
DataFlow::Node getOperandNode(int i) { result = range.getOperandNode(i) }
23+
24+
/** Gets a node holding one of the functions to be composed. */
25+
final DataFlow::Node getAnOperandNode() { result = getOperandNode(_) }
26+
27+
/**
28+
* Gets the function flowing into the `i`th function in the composition `f(g(h(...)))`.
29+
*
30+
* Note that this is the opposite of the order in which the function are invoked,
31+
* that is, `g` occurs later than `f` in `f(g(...))` but is invoked before `f`.
32+
*/
33+
final DataFlow::FunctionNode getOperandFunction(int i) {
34+
result = getOperandNode(i).getALocalSource()
35+
}
36+
37+
/** Gets any of the functions being composed. */
38+
final DataFlow::Node getAnOperandFunction() { result = getOperandFunction(_) }
39+
40+
/** Gets the number of functions being composed. */
41+
int getNumOperand() { result = range.getNumOperand() }
42+
}
43+
44+
module FunctionCompositionCall {
45+
abstract class Range extends DataFlow::CallNode {
46+
/**
47+
* Gets the function flowing into the `i`th function in the composition `f(g(h(...)))`.
48+
*/
49+
abstract DataFlow::Node getOperandNode(int i);
50+
51+
/** Gets the number of functions being composed. */
52+
abstract int getNumOperand();
2053
}
2154

2255
/**
23-
* Gets the ith function in this composition.
56+
* A function composition call that accepts its operands in an array or
57+
* via the arguments list.
58+
*
59+
* For simplicity, we model every composition function as if it supported this.
2460
*/
25-
DataFlow::FunctionNode getFunction(int i) { result.flowsTo(getArgument(i)) }
61+
private abstract class WithArrayOverloading extends Range {
62+
/** Gets the `i`th argument to the call or the `i`th array element passed into the call. */
63+
DataFlow::Node getEffectiveArgument(int i) {
64+
result = getArgument(0).(DataFlow::ArrayCreationNode).getElement(i)
65+
or
66+
not getArgument(0) instanceof DataFlow::ArrayCreationNode and
67+
result = getArgument(i)
68+
}
69+
70+
override int getNumOperand() {
71+
result = getArgument(0).(DataFlow::ArrayCreationNode).getSize()
72+
or
73+
not getArgument(0) instanceof DataFlow::ArrayCreationNode and
74+
result = getNumArgument()
75+
}
76+
}
77+
78+
/** A call whose arguments are functions `f,g,h` which are composed into `f(g(h(...))` */
79+
private class RightToLeft extends WithArrayOverloading {
80+
RightToLeft() {
81+
this = DataFlow::moduleImport(["compose-function"]).getACall()
82+
or
83+
this = DataFlow::moduleMember(["redux", "ramda"], "compose").getACall()
84+
or
85+
this = LodashUnderscore::member("flowRight").getACall()
86+
}
87+
88+
override DataFlow::Node getOperandNode(int i) {
89+
result = getEffectiveArgument(i)
90+
}
91+
}
92+
93+
/** A call whose arguments are functions `f,g,h` which are composed into `f(g(h(...))` */
94+
private class LeftToRight extends WithArrayOverloading {
95+
LeftToRight() {
96+
this = DataFlow::moduleImport("just-compose").getACall()
97+
or
98+
this = LodashUnderscore::member("flow").getACall()
99+
}
100+
101+
override DataFlow::Node getOperandNode(int i) {
102+
result = getEffectiveArgument(getNumOperand() - i - 1)
103+
}
104+
}
26105
}
27106

28107
/**
29108
* A taint step for a composed function.
30109
*/
31110
private class ComposedFunctionTaintStep extends TaintTracking::AdditionalTaintStep {
32-
ComposedFunction composed;
111+
FunctionCompositionCall composed;
33112
DataFlow::CallNode call;
34113

35114
ComposedFunctionTaintStep() {
@@ -38,25 +117,24 @@ private class ComposedFunctionTaintStep extends TaintTracking::AdditionalTaintSt
38117
}
39118

40119
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
41-
exists(int fnIndex, DataFlow::FunctionNode fn | fn = composed.getFunction(fnIndex) |
120+
exists(int fnIndex, DataFlow::FunctionNode fn | fn = composed.getOperandFunction(fnIndex) |
42121
// flow out of the composed call
43-
fnIndex = composed.getNumArgument() - 1 and
44-
pred = fn.getAReturn() and
122+
fnIndex = 0 and
123+
pred = fn.getReturnNode() and
45124
succ = this
46125
or
47-
if fnIndex = 0
48-
then
49-
// flow into the first composed function
50-
exists(int callArgIndex |
51-
pred = call.getArgument(callArgIndex) and
52-
succ = fn.getParameter(callArgIndex)
53-
)
54-
else
55-
// flow through the composed functions
56-
exists(DataFlow::FunctionNode predFn | predFn = composed.getFunction(fnIndex - 1) |
57-
pred = predFn.getAReturn() and
58-
succ = fn.getParameter(0)
59-
)
126+
// flow into the first function
127+
fnIndex = composed.getNumOperand() - 1 and
128+
exists(int callArgIndex |
129+
pred = call.getArgument(callArgIndex) and
130+
succ = fn.getParameter(callArgIndex)
131+
)
132+
or
133+
// flow through the composed functions
134+
exists(DataFlow::FunctionNode predFn | predFn = composed.getOperandFunction(fnIndex + 1) |
135+
pred = predFn.getReturnNode() and
136+
succ = fn.getParameter(0)
137+
)
60138
)
61139
}
62140
}
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
| tst.js:10:10:10:15 | source |
22
| tst.js:15:10:15:13 | f1() |
3-
| tst.js:20:10:20:23 | compose1(f2)() |
4-
| tst.js:28:10:28:27 | compose1(f3, f4)() |
5-
| tst.js:33:10:33:28 | compose1(o.f, f5)() |
6-
| tst.js:41:10:41:27 | compose1(f6, f7)() |
7-
| tst.js:49:10:49:33 | compose ... source) |
8-
| tst.js:61:10:61:40 | compose ... source) |
9-
| tst.js:66:10:66:30 | compose ... source) |
3+
| tst.js:20:10:20:24 | lcompose1(f2)() |
4+
| tst.js:28:10:28:28 | lcompose1(f3, f4)() |
5+
| tst.js:33:10:33:29 | lcompose1(o.f, f5)() |
6+
| tst.js:41:10:41:28 | lcompose1(f6, f7)() |
7+
| tst.js:49:10:49:34 | lcompos ... source) |
8+
| tst.js:61:10:61:41 | lcompos ... source) |
9+
| tst.js:66:10:66:31 | lcompos ... source) |
1010
| tst.js:89:10:89:31 | f18(und ... source) |
11-
| tst.js:94:10:94:24 | compose2(f19)() |
12-
| tst.js:99:10:99:24 | compose3(f20)() |
13-
| tst.js:104:10:104:24 | compose4(f21)() |
11+
| tst.js:94:10:94:30 | rcompos ... o.f)() |
12+
| tst.js:99:10:99:30 | lcompos ... f20)() |
13+
| tst.js:104:10:104:30 | lcompos ... f21)() |

javascript/ql/test/library-tests/frameworks/ComposedFunctions/tst.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import compose1 from 'just-compose';
2-
import compose2 from 'compose-function';
3-
import compose3 from 'lodash.flow';
1+
import lcompose1 from 'just-compose';
2+
import rcompose2 from 'compose-function';
3+
import lcompose3 from 'lodash.flow';
44
import _ from 'lodash';
5-
var compose4 = _.flow;
5+
var lcompose4 = _.flow;
66

77
(function(){
88
var source = SOURCE();
@@ -17,36 +17,36 @@ var compose4 = _.flow;
1717
function f2(){
1818
return source;
1919
}
20-
SINK(compose1(f2)());
20+
SINK(lcompose1(f2)());
2121

2222
function f3(){
2323

2424
}
2525
function f4(){
2626
return source;
2727
}
28-
SINK(compose1(f3, f4)());
28+
SINK(lcompose1(f3, f4)());
2929

3030
function f5(){
3131
return source;
3232
}
33-
SINK(compose1(o.f, f5)());
33+
SINK(lcompose1(o.f, f5)());
3434

3535
function f6(){
3636
return source;
3737
}
3838
function f7(x){
3939
return x;
4040
}
41-
SINK(compose1(f6, f7)());
41+
SINK(lcompose1(f6, f7)());
4242

4343
function f8(x){
4444
return x;
4545
}
4646
function f9(x){
4747
return x;
4848
}
49-
SINK(compose1(f8, f9)(source));
49+
SINK(lcompose1(f8, f9)(source));
5050

5151

5252
function f10(x){
@@ -58,12 +58,12 @@ var compose4 = _.flow;
5858
function f12(x){
5959
return x;
6060
}
61-
SINK(compose1(f10, f11, f12)(source));
61+
SINK(lcompose1(f10, f11, f12)(source));
6262

6363
function f13(x){
6464
return x + 'foo' ;
6565
}
66-
SINK(compose1(f13)(source));
66+
SINK(lcompose1(f13)(source));
6767

6868
function f14(){
6969
return undefined;
@@ -76,7 +76,7 @@ var compose4 = _.flow;
7676
function f16(){
7777
return undefined;
7878
}
79-
SINK(compose1(f15, f16)()); // NO FLOW
79+
SINK(lcompose1(f15, f16)()); // NO FLOW
8080

8181
function f17(x, y){
8282
return y;
@@ -91,16 +91,21 @@ var compose4 = _.flow;
9191
function f19(){
9292
return source;
9393
}
94-
SINK(compose2(f19)());
94+
SINK(rcompose2(f19, o.f)());
9595

9696
function f20(){
9797
return source;
9898
}
99-
SINK(compose3(f20)());
99+
SINK(lcompose3(f16, f20)());
100100

101101
function f21(){
102102
return source;
103103
}
104-
SINK(compose4(f21)());
104+
SINK(lcompose4(f16, f21)());
105+
106+
function f22(){
107+
return source;
108+
}
109+
SINK(lcompose3(f22, f16)()); // NO FLOW
105110

106111
})();

0 commit comments

Comments
 (0)