Skip to content

Commit 957b60f

Browse files
committed
split fuzzy read/writes on collections into 2 pseudo-properties
1 parent b1bf7f9 commit 957b60f

File tree

4 files changed

+35
-20
lines changed

4 files changed

+35
-20
lines changed

javascript/ql/src/semmle/javascript/Collections.qll

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,6 @@ abstract private class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
8181
) {
8282
this.loadStore(pred, succ, loadProp, storeProp)
8383
}
84-
85-
/**
86-
* Holds if this step on a collection can load a value with a known key.
87-
*/
88-
predicate canLoadValueWithKnownKey() { none() }
8984
}
9085

9186
/**
@@ -100,10 +95,7 @@ module CollectionsTypeTracking {
10095
exists(CollectionFlowStep step, PseudoProperty field |
10196
summary = LoadStep(field) and
10297
step.load(pred, result, field) and
103-
not (
104-
step.canLoadValueWithKnownKey() and // for a step that could load a known key,
105-
field = mapValueUnknownKey() // don't load values with an unknown key.
106-
)
98+
not field = mapValueUnknownKey() // prune unknown reads in type-tracking
10799
or
108100
summary = StoreStep(field) and
109101
step.store(pred, result, field)
@@ -191,7 +183,7 @@ private module CollectionDataFlow {
191183
) {
192184
pred = this and
193185
succ = element and
194-
fromProp = mapValueUnknownKey() and
186+
fromProp = mapValueAll() and
195187
toProp = "1"
196188
}
197189
}
@@ -205,7 +197,7 @@ private module CollectionDataFlow {
205197
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
206198
obj = this.getReceiver() and
207199
element = this.getCallback(0).getParameter(0) and
208-
prop = [setElement(), mapValueUnknownKey()]
200+
prop = [setElement(), mapValueAll()]
209201
}
210202
}
211203

@@ -219,18 +211,18 @@ private module CollectionDataFlow {
219211
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
220212
obj = this.getReceiver() and
221213
element = this and
222-
prop = mapValue(this.getArgument(0))
214+
// reading the join of known and unknown values
215+
(prop = mapValue(this.getArgument(0)) or prop = mapValueUnknownKey())
223216
}
224-
225-
override predicate canLoadValueWithKnownKey() { any() }
226217
}
227218

228219
/**
229220
* A call to the `set` method on a Map.
230221
*
231222
* If the key of the call to `set` has a known string value,
232223
* then the value will be saved into a pseudo-property corresponding to the known string value.
233-
* The value will additionally be saved into a pseudo-property corresponding to values with unknown keys.
224+
* Otherwise the value will be saved into a pseudo-property corresponding to values with unknown keys.
225+
* The value will additionally be saved into a pseudo-property corresponding to all values.
234226
*/
235227
class MapSet extends CollectionFlowStep, DataFlow::MethodCallNode {
236228
MapSet() { this.getMethodName() = "set" }
@@ -246,9 +238,11 @@ private module CollectionDataFlow {
246238
* The pseudo-property represents both values where the key is a known string value (which is encoded in the pseudo-property),
247239
* and values where the key is unknown.
248240
*
241+
* Additionally, all elements are stored into the pseudo-property `mapValueAll()`.
242+
*
249243
* The return-type is `string` as this predicate is used to define which pseudo-properties exist.
250244
*/
251-
string getAPseudoProperty() { result = [mapValue(this.getArgument(0)), mapValueUnknownKey()] }
245+
string getAPseudoProperty() { result = [mapValue(this.getArgument(0)), mapValueAll()] }
252246
}
253247

254248
/**
@@ -262,7 +256,7 @@ private module CollectionDataFlow {
262256
) {
263257
pred = this.getReceiver() and
264258
succ = this and
265-
fromProp = [mapValueUnknownKey(), setElement()] and
259+
fromProp = [mapValueAll(), setElement()] and
266260
toProp = iteratorElement()
267261
}
268262
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,11 @@ module PseudoProperties {
613613
*/
614614
string mapValueUnknownKey() { result = pseudoProperty("unknownMapValue") }
615615

616+
/**
617+
* Gets a pseudo-property for the location of all the values in a map.
618+
*/
619+
string mapValueAll() { result = pseudoProperty("allMapValues") }
620+
616621
/**
617622
* Gets a pseudo-property for the location of a map value where the key is `key`.
618623
* The string value of the `key` is encoded in the result, and there is only a result if the string value of `key` is known.

javascript/ql/test/library-tests/frameworks/Collections/test.expected

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ dataFlow
1111
| tst.js:2:16:2:23 | source() | tst.js:46:7:46:7 | e |
1212
| tst.js:2:16:2:23 | source() | tst.js:50:10:50:10 | e |
1313
| tst.js:2:16:2:23 | source() | tst.js:53:8:53:21 | map.get("key") |
14-
| tst.js:2:16:2:23 | source() | tst.js:55:8:55:28 | map.get ... nKey()) |
14+
| tst.js:2:16:2:23 | source() | tst.js:59:8:59:22 | map2.get("foo") |
15+
| tst.js:2:16:2:23 | source() | tst.js:64:8:64:26 | map3.get(unknown()) |
16+
| tst.js:2:16:2:23 | source() | tst.js:69:8:69:26 | map3.get(unknown()) |
1517
typeTracking
1618
| tst.js:2:16:2:23 | source() | tst.js:2:16:2:23 | source() |
1719
| tst.js:2:16:2:23 | source() | tst.js:6:14:6:14 | e |

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
})
1313

1414
var map = new Map();
15-
map.set("key", source); map.set(unknownKey(), source);
15+
map.set("key", source);
1616
map.forEach(v => {
1717
sink(v);
1818
});
@@ -52,5 +52,19 @@
5252

5353
sink(map.get("key")); // NOT OK.
5454
sink(map.get("nonExistingKey")); // OK.
55-
sink(map.get(unknownKey())); // NOT OK (for data-flow). OK for type-tracking.
55+
56+
// unknown write, known read
57+
var map2 = new map();
58+
map2.set(unknown(), source);
59+
sink(map2.get("foo")); // NOT OK (for data-flow). OK for type-tracking.
60+
61+
// unknown write, unknown read
62+
var map3 = new map();
63+
map3.set(unknown(), source);
64+
sink(map3.get(unknown())); // NOT OK (for data-flow). OK for type-tracking.
65+
66+
// known write, unknown read
67+
var map4 = new map();
68+
map4.set("foo", source);
69+
sink(map3.get(unknown())); // NOT OK (for data-flow). OK for type-tracking.
5670
})();

0 commit comments

Comments
 (0)