Skip to content

Commit 82ca369

Browse files
authored
Merge pull request #18005 from Napalys/napalys/ES2022-find-functions
JS: Added support for Array.prototype.[findLastIndex, findLast] ES2022 feature
2 parents 596cfcf + 64c45de commit 82ca369

File tree

11 files changed

+734
-132
lines changed

11 files changed

+734
-132
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added taint-steps for `Array.prototype.findLast`
5+
* Added taint-steps for `Array.prototype.findLastIndex`

javascript/ql/lib/semmle/javascript/Arrays.qll

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,10 @@ private module ArrayLibraries {
384384
}
385385

386386
/**
387-
* Gets a call to `Array.prototype.find` or a polyfill implementing the same functionality.
387+
* Gets a call to `Array.prototype.find` or `Array.prototype.findLast` or a polyfill implementing the same functionality.
388388
*/
389389
DataFlow::CallNode arrayFindCall(DataFlow::Node array) {
390-
result.(DataFlow::MethodCallNode).getMethodName() = "find" and
390+
result.(DataFlow::MethodCallNode).getMethodName() in ["find", "findLast"] and
391391
array = result.getReceiver()
392392
or
393393
result = DataFlow::moduleImport(["array.prototype.find", "array-find"]).getACall() and
@@ -483,4 +483,31 @@ private module ArrayLibraries {
483483
)
484484
}
485485
}
486+
487+
/**
488+
* Defines a data flow step that tracks the flow of data through callback functions in arrays.
489+
*/
490+
private class ArrayCallBackDataFlowStep extends PreCallGraphStep {
491+
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
492+
exists(DataFlow::MethodCallNode call |
493+
call.getMethodName() = ["findLast", "find", "findLastIndex"] and
494+
prop = arrayLikeElement() and
495+
obj = call.getReceiver() and
496+
element = call.getCallback(0).getParameter(0)
497+
)
498+
}
499+
}
500+
501+
/**
502+
* This step models the propagation of data from the array to the callback function's parameter.
503+
*/
504+
private class ArrayCallBackDataTaintStep extends TaintTracking::SharedTaintStep {
505+
override predicate step(DataFlow::Node obj, DataFlow::Node element) {
506+
exists(DataFlow::MethodCallNode call |
507+
call.getMethodName() = ["findLast", "find", "findLastIndex"] and
508+
obj = call.getReceiver() and
509+
element = call.getCallback(0).getParameter(0)
510+
)
511+
}
512+
}
486513
}

javascript/ql/src/Statements/UseOfReturnlessFunction.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ predicate hasNonVoidCallbackMethod(string name) {
114114
name =
115115
[
116116
"every", "filter", "find", "findIndex", "flatMap", "map", "reduce", "reduceRight", "some",
117-
"sort"
117+
"sort", "findLastIndex", "findLast"
118118
]
119119
}
120120

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
| arrays.js:2:16:2:23 | "source" | arrays.js:90:10:90:10 | x |
1515
| arrays.js:2:16:2:23 | "source" | arrays.js:93:8:93:17 | arr.at(-1) |
1616
| arrays.js:2:16:2:23 | "source" | arrays.js:109:8:109:24 | arr8_spread.pop() |
17+
| arrays.js:2:16:2:23 | "source" | arrays.js:111:8:111:33 | arr.fin ... llback) |
1718
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
1819
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
1920
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |
@@ -25,3 +26,8 @@
2526
| arrays.js:53:4:53:11 | "source" | arrays.js:54:10:54:18 | ary.pop() |
2627
| arrays.js:99:31:99:38 | "source" | arrays.js:100:8:100:17 | arr8.pop() |
2728
| arrays.js:103:55:103:62 | "source" | arrays.js:105:8:105:25 | arr8_variant.pop() |
29+
| arrays.js:114:19:114:26 | "source" | arrays.js:115:50:115:53 | item |
30+
| arrays.js:114:19:114:26 | "source" | arrays.js:116:10:116:16 | element |
31+
| arrays.js:120:19:120:26 | "source" | arrays.js:121:46:121:49 | item |
32+
| arrays.js:120:19:120:26 | "source" | arrays.js:122:10:122:16 | element |
33+
| arrays.js:126:19:126:26 | "source" | arrays.js:127:55:127:58 | item |

javascript/ql/test/library-tests/Arrays/DataFlow.ql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import javascript
33
class ArrayFlowConfig extends DataFlow::Configuration {
44
ArrayFlowConfig() { this = "ArrayFlowConfig" }
55

6-
override predicate isSource(DataFlow::Node source) { source.asExpr().getStringValue() = "source" }
6+
override predicate isSource(DataFlow::Node source) {
7+
source.asExpr().getStringValue() = "source" or
8+
source.(DataFlow::CallNode).getCalleeName() = "source"
9+
}
710

811
override predicate isSink(DataFlow::Node sink) {
912
sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument()

javascript/ql/test/library-tests/Arrays/TaintFlow.expected

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
| arrays.js:2:16:2:23 | "source" | arrays.js:90:10:90:10 | x |
1616
| arrays.js:2:16:2:23 | "source" | arrays.js:93:8:93:17 | arr.at(-1) |
1717
| arrays.js:2:16:2:23 | "source" | arrays.js:109:8:109:24 | arr8_spread.pop() |
18+
| arrays.js:2:16:2:23 | "source" | arrays.js:111:8:111:33 | arr.fin ... llback) |
1819
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
1920
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
2021
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |
@@ -29,3 +30,13 @@
2930
| arrays.js:96:9:96:16 | "source" | arrays.js:96:8:96:36 | ["sourc ... => !!x) |
3031
| arrays.js:99:31:99:38 | "source" | arrays.js:100:8:100:17 | arr8.pop() |
3132
| arrays.js:103:55:103:62 | "source" | arrays.js:105:8:105:25 | arr8_variant.pop() |
33+
| arrays.js:114:19:114:26 | "source" | arrays.js:115:50:115:53 | item |
34+
| arrays.js:114:19:114:26 | "source" | arrays.js:116:10:116:16 | element |
35+
| arrays.js:120:19:120:26 | "source" | arrays.js:121:46:121:49 | item |
36+
| arrays.js:120:19:120:26 | "source" | arrays.js:122:10:122:16 | element |
37+
| arrays.js:126:19:126:26 | "source" | arrays.js:127:55:127:58 | item |
38+
| arrays.js:131:17:131:24 | source() | arrays.js:132:46:132:49 | item |
39+
| arrays.js:131:17:131:24 | source() | arrays.js:133:10:133:17 | element1 |
40+
| arrays.js:137:17:137:24 | source() | arrays.js:138:50:138:53 | item |
41+
| arrays.js:137:17:137:24 | source() | arrays.js:139:10:139:17 | element1 |
42+
| arrays.js:143:17:143:24 | source() | arrays.js:144:55:144:58 | item |

javascript/ql/test/library-tests/Arrays/TaintFlow.ql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import javascript
33
class ArrayTaintFlowConfig extends TaintTracking::Configuration {
44
ArrayTaintFlowConfig() { this = "ArrayTaintFlowConfig" }
55

6-
override predicate isSource(DataFlow::Node source) { source.asExpr().getStringValue() = "source" }
6+
override predicate isSource(DataFlow::Node source) {
7+
source.asExpr().getStringValue() = "source" or
8+
source.(DataFlow::CallNode).getCalleeName() = "source"
9+
}
710

811
override predicate isSink(DataFlow::Node sink) {
912
sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument()

javascript/ql/test/library-tests/Arrays/arrays.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,41 @@
107107
var arr8_spread = [];
108108
arr8_spread = arr8_spread.toSpliced(0, 0, ...arr);
109109
sink(arr8_spread.pop()); // NOT OK
110+
111+
sink(arr.findLast(someCallback)); // NOT OK
112+
113+
{ // Test for findLast function
114+
const list = ["source"];
115+
const element = list.findLast((item) => sink(item)); // NOT OK
116+
sink(element); // NOT OK
117+
}
118+
119+
{ // Test for find function
120+
const list = ["source"];
121+
const element = list.find((item) => sink(item)); // NOT OK
122+
sink(element); // NOT OK
123+
}
124+
125+
{ // Test for findLastIndex function
126+
const list = ["source"];
127+
const element = list.findLastIndex((item) => sink(item)); // NOT OK
128+
sink(element); // OK
129+
}
130+
{
131+
const arr = source();
132+
const element1 = arr.find((item) => sink(item)); // NOT OK
133+
sink(element1); // NOT OK
134+
}
135+
136+
{
137+
const arr = source();
138+
const element1 = arr.findLast((item) => sink(item)); // NOT OK
139+
sink(element1); // NOT OK
140+
}
141+
142+
{
143+
const arr = source();
144+
const element1 = arr.findLastIndex((item) => sink(item)); // NOT OK
145+
sink(element1); // OK
146+
}
110147
});

0 commit comments

Comments
 (0)