Skip to content

Commit 25aea90

Browse files
committed
add more dataflow steps for Arrays
1 parent a02213e commit 25aea90

5 files changed

Lines changed: 112 additions & 3 deletions

File tree

javascript/ql/src/semmle/javascript/Arrays.qll

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,55 @@ module ArrayTaintTracking {
9696
* Classes and predicates for modelling data-flow for arrays.
9797
*/
9898
private module ArrayDataFlow {
99+
private import DataFlow::PseudoProperties
100+
99101
/**
100-
* Gets a pseudo-field representing an element inside an array.
102+
* A step modelling the creation of an Array using the `Array.from(x)` method.
103+
* The step copies the elements of the argument (set, array, or iterator elements) into the resulting array.
101104
*/
102-
private string arrayElement() { result = "$arrayElement$" }
105+
private class ArrayFrom extends DataFlow::AdditionalFlowStep, DataFlow::CallNode {
106+
ArrayFrom() { this = DataFlow::globalVarRef("Array").getAMemberCall("from") }
107+
108+
override predicate loadStoreStep(
109+
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
110+
) {
111+
pred = this.getArgument(0) and
112+
succ = this and
113+
fromProp = arrayLikeElement() and
114+
toProp = arrayElement()
115+
}
116+
}
117+
118+
/**
119+
* A step modelling an array copy where the spread operator is used.
120+
* The result is essentially array concatenation.
121+
*
122+
* Such a step can occur both with the `push` and `unshift` methods, or when creating a new array.
123+
*/
124+
private class ArrayCopySpread extends DataFlow::AdditionalFlowStep {
125+
DataFlow::Node spreadArgument; // the spread argument containing the elements to be copied.
126+
DataFlow::Node base; // the object where the elements should be copied to.
127+
128+
ArrayCopySpread() {
129+
exists(DataFlow::MethodCallNode mcn | mcn = this |
130+
mcn.getMethodName() = ["push", "unshift"] and
131+
spreadArgument = mcn.getASpreadArgument() and
132+
base = mcn.getReceiver().getALocalSource()
133+
)
134+
or
135+
spreadArgument = this.(DataFlow::ArrayCreationNode).getASpreadArgument() and
136+
base = this
137+
}
138+
139+
override predicate loadStoreStep(
140+
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
141+
) {
142+
pred = spreadArgument and
143+
succ = base and
144+
fromProp = arrayLikeElement() and
145+
toProp = arrayElement()
146+
}
147+
}
103148

104149
/**
105150
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
@@ -112,7 +157,7 @@ private module ArrayDataFlow {
112157

113158
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
114159
prop = arrayElement() and
115-
(element = this.getAnArgument() or element = this.getASpreadArgument()) and
160+
element = this.getAnArgument() and
116161
obj = this.getReceiver().getALocalSource()
117162
}
118163
}
@@ -260,4 +305,20 @@ private module ArrayDataFlow {
260305
succ = this
261306
}
262307
}
308+
309+
/**
310+
* A step for modelling `for of` iteration on arrays.
311+
*/
312+
private class ForOfStep extends DataFlow::AdditionalFlowStep, DataFlow::ValueNode {
313+
ForOfStmt forOf;
314+
DataFlow::Node element;
315+
316+
ForOfStep() { this.asExpr() = forOf.getIterationDomain() }
317+
318+
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
319+
obj = this and
320+
e = DataFlow::lvalueNode(forOf.getLValue()) and
321+
prop = arrayElement()
322+
}
323+
}
263324
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,22 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
574574
}
575575
}
576576

577+
/**
578+
* A collection of pseudo-properties that are used in multiple files.
579+
* For use with load/store steps in `DataFlow::AdditionalFlowStep` and TypeTracking.
580+
*/
581+
module PseudoProperties {
582+
/**
583+
* Gets a pseudo-field representing an elements inside an `Array`.
584+
*/
585+
string arrayElement() { result = "$arrayElement$" }
586+
587+
/**
588+
* Gets a pseudo-property representing elements inside some array-like object.
589+
*/
590+
string arrayLikeElement() { result = arrayElement() }
591+
}
592+
577593
/**
578594
* A data flow node that should be considered a source for some specific configuration,
579595
* in addition to any other sources that configuration may recognize.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,16 @@ class ArrayCreationNode extends DataFlow::ValueNode, DataFlow::SourceNode {
620620
result = this.(ArrayLiteralNode).getSize() or
621621
result = this.(ArrayConstructorInvokeNode).getSize()
622622
}
623+
624+
/**
625+
* Gets a data flow node corresponding to an array of values being passed as
626+
* individual arguments to this array creation.
627+
*/
628+
DataFlow::Node getASpreadArgument() {
629+
exists(SpreadElement arg | arg = getAnElement().getEnclosingExpr() |
630+
result = DataFlow::valueNode(arg.getOperand())
631+
)
632+
}
623633
}
624634

625635
/**

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
| arrays.js:2:16:2:23 | "source" | arrays.js:15:27:15:27 | e |
44
| arrays.js:2:16:2:23 | "source" | arrays.js:16:23:16:23 | e |
55
| arrays.js:2:16:2:23 | "source" | arrays.js:20:8:20:16 | arr.pop() |
6+
| arrays.js:2:16:2:23 | "source" | arrays.js:52:10:52:10 | x |
7+
| arrays.js:2:16:2:23 | "source" | arrays.js:56:10:56:10 | x |
8+
| arrays.js:2:16:2:23 | "source" | arrays.js:60:10:60:10 | x |
9+
| arrays.js:2:16:2:23 | "source" | arrays.js:66:10:66:10 | x |
610
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
711
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
812
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,22 @@
4747
});
4848

4949
sink(arr[0]); // OK - tuple like usage.
50+
51+
for (const x of arr) {
52+
sink(x); // NOT OK
53+
}
54+
55+
for (const x of Array.from(arr)) {
56+
sink(x); // NOT OK
57+
}
58+
59+
for (const x of [...arr]) {
60+
sink(x); // NOT OK
61+
}
62+
63+
var arr7 = [];
64+
arr7.push(...arr);
65+
for (const x of arr7) {
66+
sink(x); // NOT OK
67+
}
5068
});

0 commit comments

Comments
 (0)