@@ -25,6 +25,18 @@ module AsyncPackage {
2525 result = member ( name + "Series" )
2626 }
2727
28+ /**
29+ * Gets `Limit` or `Series` name variants for a given member name.
30+ *
31+ * For example, `memberNameVariant("map")` returns `map`, `mapLimit`, and `mapSeries`.
32+ */
33+ bindingset [ name]
34+ private string memberNameVariant ( string name ) {
35+ result = name or
36+ result = name + "Limit" or
37+ result = name + "Series"
38+ }
39+
2840 /**
2941 * A call to `async.waterfall`.
3042 */
@@ -101,22 +113,47 @@ module AsyncPackage {
101113 */
102114 class IterationCall extends DataFlow:: InvokeNode {
103115 string name ;
116+ int iteratorCallbackIndex ;
117+ int finalCallbackIndex ;
104118
105119 IterationCall ( ) {
106- this = memberVariant ( name ) .getACall ( ) and
107- name =
108- [
109- "concat" , "detect" , "each" , "eachOf" , "forEach" , "forEachOf" , "every" , "filter" ,
110- "groupBy" , "map" , "mapValues" , "reduce" , "reduceRight" , "reject" , "some" , "sortBy" ,
111- "transform"
112- ]
120+ (
121+ (
122+ name =
123+ memberNameVariant ( [
124+ "concat" , "detect" , "each" , "eachOf" , "forEach" , "forEachOf" , "every" , "filter" ,
125+ "groupBy" , "map" , "mapValues" , "reject" , "some" , "sortBy" ,
126+ ] ) and
127+ if name .matches ( "%Limit" )
128+ then (
129+ iteratorCallbackIndex = 2 and finalCallbackIndex = 3
130+ ) else (
131+ iteratorCallbackIndex = 1 and finalCallbackIndex = 2
132+ )
133+ )
134+ or
135+ name = [ "reduce" , "reduceRight" , "transform" ] and
136+ iteratorCallbackIndex = 2 and
137+ finalCallbackIndex = 3
138+ ) and
139+ this = member ( name ) .getACall ( )
113140 }
114141
115142 /**
116- * Gets the name of the iteration call, without the `Limit` or `Series` suffix.
143+ * Gets the name of the iteration call
117144 */
118145 string getName ( ) { result = name }
119146
147+ /**
148+ * Gets the iterator callback index
149+ */
150+ int getIteratorCallbackIndex ( ) { result = iteratorCallbackIndex }
151+
152+ /**
153+ * Gets the final callback index
154+ */
155+ int getFinalCallbackIndex ( ) { result = finalCallbackIndex }
156+
120157 /**
121158 * Gets the node holding the collection being iterated over.
122159 */
@@ -125,41 +162,48 @@ module AsyncPackage {
125162 /**
126163 * Gets the node holding the function being called for each element in the collection.
127164 */
128- DataFlow:: Node getIteratorCallback ( ) { result = this .getArgument ( this .getNumArgument ( ) - 2 ) }
165+ DataFlow:: FunctionNode getIteratorCallback ( ) {
166+ result = this .getCallback ( iteratorCallbackIndex )
167+ }
129168
130169 /**
131- * Gets the node holding the function being invoked after iteration is complete.
170+ * Gets the node holding the function being invoked after iteration is complete. (may not exist)
132171 */
133- DataFlow:: Node getFinalCallback ( ) { result = this .getArgument ( this . getNumArgument ( ) - 1 ) }
172+ DataFlow:: FunctionNode getFinalCallback ( ) { result = this .getCallback ( finalCallbackIndex ) }
134173 }
135174
136- /**
137- * A taint step from the collection into the iterator callback of an iteration call.
138- *
139- * For example: `data -> item` in `async.each(data, (item, cb) => {})`.
140- */
141- private class IterationInputTaintStep extends TaintTracking:: SharedTaintStep {
142- override predicate step ( DataFlow:: Node pred , DataFlow:: Node succ ) {
143- exists ( DataFlow:: FunctionNode iteratee , IterationCall call |
144- iteratee = call .getIteratorCallback ( ) and // Require a closure to avoid spurious call/return mismatch.
145- pred = call .getCollection ( ) and // TODO: needs a flow summary to ensure ArrayElement content is unfolded
146- succ = iteratee .getParameter ( 0 )
147- )
175+ private class IterationCallFlowSummary extends DataFlow:: SummarizedCallable {
176+ private int callbackArgIndex ;
177+
178+ IterationCallFlowSummary ( ) {
179+ this = "async.IteratorCall(callbackArgIndex=" + callbackArgIndex + ")" and
180+ callbackArgIndex in [ 1 .. 3 ]
181+ }
182+
183+ override DataFlow:: InvokeNode getACallSimple ( ) {
184+ result instanceof IterationCall and
185+ result .( IterationCall ) .getIteratorCallbackIndex ( ) = callbackArgIndex
186+ }
187+
188+ override predicate propagatesFlow ( string input , string output , boolean preservesValue ) {
189+ preservesValue = true and
190+ input = "Argument[0]." + [ "ArrayElement" , "SetElement" , "IteratorElement" , "AnyMember" ] and
191+ output = "Argument[" + callbackArgIndex + "].Parameter[0]"
148192 }
149193 }
150194
151195 /**
152196 * A taint step from the return value of an iterator callback to the result of the iteration
153197 * call.
154198 *
155- * For example: `item + taint()` -> result` in `async.map(data, (item, cb) => cb(null, item + taint()), (err, result) => {})`.
199+ * For example: `item + taint() -> result` in `async.map(data, (item, cb) => cb(null, item + taint()), (err, result) => {})`.
156200 */
157201 private class IterationOutputTaintStep extends TaintTracking:: SharedTaintStep {
158202 override predicate step ( DataFlow:: Node pred , DataFlow:: Node succ ) {
159203 exists (
160204 DataFlow:: FunctionNode iteratee , DataFlow:: FunctionNode final , int i , IterationCall call
161205 |
162- iteratee = call .getIteratorCallback ( ) . getALocalSource ( ) and
206+ iteratee = call .getIteratorCallback ( ) and
163207 final = call .getFinalCallback ( ) and // Require a closure to avoid spurious call/return mismatch.
164208 pred = getLastParameter ( iteratee ) .getACall ( ) .getArgument ( i ) and
165209 succ = final .getParameter ( i ) and
@@ -175,14 +219,18 @@ module AsyncPackage {
175219 *
176220 * For example: `data -> result` in `async.sortBy(data, orderingFn, (err, result) => {})`.
177221 */
178- private class IterationPreserveTaintStep extends TaintTracking:: SharedTaintStep {
179- override predicate step ( DataFlow:: Node pred , DataFlow:: Node succ ) {
180- exists ( DataFlow:: FunctionNode final , IterationCall call |
181- final = call .getFinalCallback ( ) and // Require a closure to avoid spurious call/return mismatch.
182- pred = call .getCollection ( ) and
183- succ = final .getParameter ( 1 ) and
184- call .getName ( ) = "sortBy"
185- )
222+ private class IterationPreserveTaintStepFlowSummary extends DataFlow:: SummarizedCallable {
223+ IterationPreserveTaintStepFlowSummary ( ) { this = "async.sortBy" }
224+
225+ override DataFlow:: InvokeNode getACallSimple ( ) {
226+ result instanceof IterationCall and
227+ result .( IterationCall ) .getName ( ) = "sortBy"
228+ }
229+
230+ override predicate propagatesFlow ( string input , string output , boolean preservesValue ) {
231+ preservesValue = false and
232+ input = "Argument[0]." + [ "ArrayElement" , "SetElement" , "IteratorElement" , "AnyMember" ] and
233+ output = "Argument[2].Parameter[1]"
186234 }
187235 }
188236}
0 commit comments