Skip to content

Commit 63bc1ef

Browse files
authored
Merge pull request #17977 from Napalys/napalys/toSpliced-support
JS: Added support for Array.prototype.toSpliced() ES2023 feature
2 parents e081b9a + 631a377 commit 63bc1ef

File tree

13 files changed

+440
-117
lines changed

13 files changed

+440
-117
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
Added taint-steps for `Array.prototype.toSpliced`

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,23 @@ module ArrayTaintTracking {
8181
pred = call.getArgument(any(int i | i >= 2)) and
8282
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
8383
or
84+
// `array.toSpliced(x, y, source())`: if `source()` is tainted, then so is the result of `toSpliced`, but not the original array.
85+
call.(DataFlow::MethodCallNode).getMethodName() = "toSpliced" and
86+
pred = call.getArgument(any(int i | i >= 2)) and
87+
succ = call
88+
or
8489
// `array.splice(i, del, ...e)`: if `e` is tainted, then so is `array`.
8590
pred = call.getASpreadArgument() and
8691
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
8792
or
93+
// `array.toSpliced(i, del, ...e)`: if `e` is tainted, then so is the result of `toSpliced`, but not the original array.
94+
pred = call.getASpreadArgument() and
95+
call.(DataFlow::MethodCallNode).getMethodName() = "toSpliced" and
96+
succ = call
97+
or
8898
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
89-
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice", "at"]) and
99+
call.(DataFlow::MethodCallNode)
100+
.calls(pred, ["pop", "shift", "slice", "splice", "at", "toSpliced"]) and
90101
succ = call
91102
or
92103
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
@@ -283,7 +294,7 @@ private module ArrayDataFlow {
283294
private class ArraySpliceStep extends PreCallGraphStep {
284295
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
285296
exists(DataFlow::MethodCallNode call |
286-
call.getMethodName() = "splice" and
297+
call.getMethodName() = ["splice", "toSpliced"] and
287298
prop = arrayElement() and
288299
element = call.getArgument(any(int i | i >= 2)) and
289300
call = obj.getAMethodCall()
@@ -297,7 +308,7 @@ private module ArrayDataFlow {
297308
toProp = arrayElement() and
298309
// `array.splice(i, del, ...arr)` variant
299310
exists(DataFlow::MethodCallNode mcn |
300-
mcn.getMethodName() = "splice" and
311+
mcn.getMethodName() = ["splice", "toSpliced"] and
301312
pred = mcn.getASpreadArgument() and
302313
succ = mcn.getReceiver().getALocalSource()
303314
)
@@ -320,12 +331,12 @@ private module ArrayDataFlow {
320331
}
321332

322333
/**
323-
* A step for modeling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
334+
* A step for modeling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`/`toSpliced`.
324335
*/
325336
private class ArraySliceStep extends PreCallGraphStep {
326337
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
327338
exists(DataFlow::MethodCallNode call |
328-
call.getMethodName() = ["slice", "splice", "filter"] and
339+
call.getMethodName() = ["slice", "splice", "filter", "toSpliced"] and
329340
prop = arrayElement() and
330341
pred = call.getReceiver() and
331342
succ = call

javascript/ql/lib/semmle/javascript/Regexp.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,7 @@ private predicate isUsedAsNumber(DataFlow::LocalSourceNode value) {
972972
or
973973
exists(DataFlow::CallNode call |
974974
call.getCalleeName() =
975-
["substring", "substr", "slice", "splice", "charAt", "charCodeAt", "codePointAt"] and
975+
["substring", "substr", "slice", "splice", "charAt", "charCodeAt", "codePointAt", "toSpliced"] and
976976
value.flowsTo(call.getAnArgument())
977977
)
978978
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
| arrays.js:2:16:2:23 | "source" | arrays.js:86:8:86:35 | arrayFi ... llback) |
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) |
16+
| arrays.js:2:16:2:23 | "source" | arrays.js:109:8:109:24 | arr8_spread.pop() |
1617
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
1718
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
1819
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |
@@ -22,3 +23,5 @@
2223
| arrays.js:29:21:29:28 | "source" | arrays.js:50:8:50:17 | arr6.pop() |
2324
| arrays.js:33:37:33:44 | "source" | arrays.js:35:8:35:25 | arr4_variant.pop() |
2425
| arrays.js:53:4:53:11 | "source" | arrays.js:54:10:54:18 | ary.pop() |
26+
| arrays.js:99:31:99:38 | "source" | arrays.js:100:8:100:17 | arr8.pop() |
27+
| arrays.js:103:55:103:62 | "source" | arrays.js:105:8:105:25 | arr8_variant.pop() |

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

Lines changed: 3 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:86:8:86:35 | arrayFi ... llback) |
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) |
17+
| arrays.js:2:16:2:23 | "source" | arrays.js:109:8:109:24 | arr8_spread.pop() |
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() |
@@ -26,3 +27,5 @@
2627
| arrays.js:53:4:53:11 | "source" | arrays.js:55:10:55:12 | ary |
2728
| arrays.js:95:9:95:16 | "source" | arrays.js:95:8:95:34 | ["sourc ... ) => x) |
2829
| arrays.js:96:9:96:16 | "source" | arrays.js:96:8:96:36 | ["sourc ... => !!x) |
30+
| arrays.js:99:31:99:38 | "source" | arrays.js:100:8:100:17 | arr8.pop() |
31+
| arrays.js:103:55:103:62 | "source" | arrays.js:105:8:105:25 | arr8_variant.pop() |

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,17 @@
9494

9595
sink(["source"].filter((x) => x)); // NOT OK
9696
sink(["source"].filter((x) => !!x)); // NOT OK
97+
98+
var arr8 = [];
99+
arr8 = arr8.toSpliced(0, 0, "source");
100+
sink(arr8.pop()); // NOT OK
101+
102+
var arr8_variant = [];
103+
arr8_variant = arr8_variant.toSpliced(0, 0, "safe", "source");
104+
arr8_variant.pop();
105+
sink(arr8_variant.pop()); // NOT OK
106+
107+
var arr8_spread = [];
108+
arr8_spread = arr8_spread.toSpliced(0, 0, ...arr);
109+
sink(arr8_spread.pop()); // NOT OK
97110
});

0 commit comments

Comments
 (0)