Skip to content

Commit 6e074c3

Browse files
committed
JS: Port lodash callback steps to flow summaries
Not all of lodash, just the callbacks we already modeled plus a few easy ones
1 parent 4e325d9 commit 6e074c3

File tree

1 file changed

+170
-1
lines changed

1 file changed

+170
-1
lines changed

javascript/ql/lib/semmle/javascript/frameworks/LodashUnderscore.qll

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,13 @@ module LodashUnderscore {
144144
name = ["union", "zip"] and
145145
pred = call.getAnArgument() and
146146
succ = call
147-
or
147+
)
148+
}
149+
150+
private predicate underscoreTaintStepLegacy(DataFlow::Node pred, DataFlow::Node succ) {
151+
exists(string name, DataFlow::CallNode call |
152+
call = any(Member member | member.getName() = name).getACall()
153+
|
148154
name =
149155
["each", "map", "every", "some", "max", "min", "sortBy", "partition", "mapObject", "tap"] and
150156
pred = call.getArgument(0) and
@@ -168,6 +174,169 @@ module LodashUnderscore {
168174
underscoreTaintStep(pred, succ)
169175
}
170176
}
177+
178+
private class UnderscoreTaintStepLegacy extends TaintTracking::LegacyTaintStep {
179+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
180+
underscoreTaintStepLegacy(pred, succ)
181+
}
182+
}
183+
184+
private class LodashEach extends DataFlow::SummarizedCallable {
185+
LodashEach() { this = "_.each-like" }
186+
187+
override DataFlow::CallNode getACall() {
188+
result = member(["each", "eachRight", "forEach", "forEachRight", "every", "some"]).getACall()
189+
}
190+
191+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
192+
preservesValue = true and
193+
input = "Argument[0].ArrayElement" and
194+
output = "Argument[1].Parameter[0]"
195+
}
196+
}
197+
198+
private class LodashMap extends DataFlow::SummarizedCallable {
199+
LodashMap() { this = "_.map" }
200+
201+
override DataFlow::CallNode getACall() { result = member("map").getACall() }
202+
203+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
204+
(
205+
input = "Argument[0].ArrayElement" and
206+
output = "Argument[1].Parameter[0]"
207+
or
208+
input = "Argument[1].ReturnValue" and
209+
output = "ReturnValue.ArrayElement"
210+
) and
211+
preservesValue = true
212+
}
213+
}
214+
215+
private class LodashFlatMap extends DataFlow::SummarizedCallable {
216+
LodashFlatMap() { this = "_.flatMap" }
217+
218+
override DataFlow::CallNode getACall() { result = member("flatMap").getACall() }
219+
220+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
221+
(
222+
input = "Argument[0].ArrayElement" and
223+
output = "Argument[1].Parameter[0]"
224+
or
225+
input = "Argument[1].ReturnValue.WithoutArrayElement" and
226+
output = "ReturnValue.ArrayElement"
227+
or
228+
input = "Argument[1].ReturnValue.ArrayElement" and
229+
output = "ReturnValue.ArrayElement"
230+
) and
231+
preservesValue = true
232+
}
233+
}
234+
235+
private class LodashFlatMapDeep extends DataFlow::SummarizedCallable {
236+
LodashFlatMapDeep() { this = "_.flatMapDeep" }
237+
238+
override DataFlow::CallNode getACall() {
239+
result = member(["flatMapDeep", "flatMapDepth"]).getACall()
240+
}
241+
242+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
243+
(
244+
input = "Argument[0].ArrayElement" and
245+
output = "Argument[1].Parameter[0]"
246+
or
247+
input = "Argument[1].ReturnValue.WithoutArrayElement" and
248+
output = "ReturnValue.ArrayElement"
249+
or
250+
input = "Argument[1].ReturnValue.ArrayElementDeep" and
251+
output = "ReturnValue.ArrayElement"
252+
) and
253+
preservesValue = true
254+
}
255+
}
256+
257+
private class LodashReduce extends DataFlow::SummarizedCallable {
258+
LodashReduce() { this = "_.reduce-like" }
259+
260+
override DataFlow::CallNode getACall() { result = member(["reduce", "reduceRight"]).getACall() }
261+
262+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
263+
(
264+
input = "Argument[0].ArrayElement" and
265+
output = "Argument[1].Parameter[1]"
266+
or
267+
input = ["Argument[1].ReturnValue", "Argument[2]"] and
268+
output = ["ReturnValue", "Argument[1].Parameter[0]"]
269+
) and
270+
preservesValue = true
271+
}
272+
}
273+
274+
private class LoashSortBy extends DataFlow::SummarizedCallable {
275+
LoashSortBy() { this = "_.sortBy-like" }
276+
277+
override DataFlow::CallNode getACall() { result = member(["sortBy", "orderBy"]).getACall() }
278+
279+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
280+
input = "Argument[0].ArrayElement" and
281+
output = ["Argument[1].Parameter[1]", "ReturnValue.ArrayElement"] and
282+
preservesValue = true
283+
}
284+
}
285+
286+
private class LodashMinMaxBy extends DataFlow::SummarizedCallable {
287+
LodashMinMaxBy() { this = "_.minBy / _.maxBy" }
288+
289+
override DataFlow::CallNode getACall() { result = member(["minBy", "maxBy"]).getACall() }
290+
291+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
292+
input = "Argument[0].ArrayElement" and
293+
output = ["Argument[1].Parameter[1]", "ReturnValue"] and
294+
preservesValue = true
295+
}
296+
}
297+
298+
private class LodashPartition extends DataFlow::SummarizedCallable {
299+
LodashPartition() { this = "_.partition" }
300+
301+
override DataFlow::CallNode getACall() { result = member(["partition"]).getACall() }
302+
303+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
304+
input = "Argument[0].ArrayElement" and
305+
output = ["Argument[1].Parameter[1]", "ReturnValue.ArrayElement.ArrayElement"] and
306+
preservesValue = true
307+
}
308+
}
309+
310+
private class UnderscoreMapObject extends DataFlow::SummarizedCallable {
311+
UnderscoreMapObject() { this = "_.mapObject" }
312+
313+
override DataFlow::CallNode getACall() { result = member("mapObject").getACall() }
314+
315+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
316+
// Just collapse all properties with AnyMember. We could be more precise by generating a summary
317+
// for each property name, but for a rarely-used method like this it dosn't seem worth it.
318+
(
319+
input = "Argument[0].AnyMember" and
320+
output = "Argument[1].Parameter[1]"
321+
or
322+
input = "Argument[1].ReturnValue" and
323+
output = "ReturnValue.AnyMember"
324+
) and
325+
preservesValue = true
326+
}
327+
}
328+
329+
private class LodashTap extends DataFlow::SummarizedCallable {
330+
LodashTap() { this = "_.tap" }
331+
332+
override DataFlow::CallNode getACall() { result = member("tap").getACall() }
333+
334+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
335+
input = "Argument[0]" and
336+
output = ["Argument[1].Parameter[0]", "ReturnValue"] and
337+
preservesValue = true
338+
}
339+
}
171340
}
172341

173342
/**

0 commit comments

Comments
 (0)