@@ -209,10 +209,49 @@ private module Mongoose {
209209 result = getAMongooseInstance ( ) .getAMemberCall ( "createConnection" )
210210 }
211211
212+ /**
213+ * A common interface for various Mongoose calls.
214+ */
215+ private class CommonInterface extends DataFlow:: InvokeNode {
216+ /**
217+ * Holds if this call returns an object of type `Query`.
218+ */
219+ abstract predicate returnsQuery ( ) ;
220+
221+ /**
222+ * Holds if this call returns a `Query` that evaluates to a one or
223+ * more Documents (`asArray` is false if it evaluates to a single
224+ * Document).
225+ */
226+ abstract predicate returnsDocumentQuery ( boolean asArray ) ;
227+
228+ /**
229+ * Holds if this call interprets `arg` as a query.
230+ */
231+ abstract predicate interpretsArgumentAsQuery ( DataFlow:: Node arg ) ;
232+ }
233+
212234 /**
213235 * Provides classes modeling the Mongoose Model class
214236 */
215237 module Model {
238+ private class ModelAsCommonInterface extends CommonInterface , DataFlow:: MethodCallNode {
239+ ModelAsCommonInterface ( ) { this = ref ( ) .getAMethodCall ( ) }
240+
241+ override predicate returnsQuery ( ) { MethodSignatures:: returnsQuery ( getMethodName ( ) ) }
242+
243+ override predicate returnsDocumentQuery ( boolean asArray ) {
244+ MethodSignatures:: returnsDocumentQuery ( getMethodName ( ) , asArray )
245+ }
246+
247+ override predicate interpretsArgumentAsQuery ( DataFlow:: Node arg ) {
248+ exists ( int n |
249+ MethodSignatures:: interpretsArgumentAsQuery ( this .getMethodName ( ) , n ) and
250+ arg = this .getArgument ( n )
251+ )
252+ }
253+ }
254+
216255 /**
217256 * Gets a data flow node referring to a Mongoose Model object.
218257 */
@@ -279,10 +318,11 @@ private module Mongoose {
279318 }
280319
281320 /**
282- * Holds if Model method `name` returns one or more documents, the
283- * documents are wrapped in an array if `asArray` is true.
321+ * Holds if Document method `name` returns a query that results in
322+ * one or more documents, the documents are wrapped in an array
323+ * if `asArray` is true.
284324 */
285- predicate returnsDocument ( string name , boolean asArray ) {
325+ predicate returnsDocumentQuery ( string name , boolean asArray ) {
286326 asArray = false and name = "findOne"
287327 or
288328 asArray = true and name = "find"
@@ -294,47 +334,41 @@ private module Mongoose {
294334 * Provides classes modeling the Mongoose Query class
295335 */
296336 module Query {
297- /**
298- * A Mongoose query object as a result of a Model method call.
299- */
300- private class QueryFromModel extends DataFlow:: MethodCallNode {
301- QueryFromModel ( ) {
302- exists ( string name |
303- Model:: MethodSignatures:: returnsQuery ( name ) and
304- Model:: ref ( ) .getAMethodCall ( name ) = this
305- )
337+ private class QueryAsCommonInterface extends CommonInterface , DataFlow:: MethodCallNode {
338+ QueryAsCommonInterface ( ) { this = ref ( ) .getAMethodCall ( ) }
339+
340+ override predicate returnsQuery ( ) { MethodSignatures:: returnsQuery ( getMethodName ( ) ) }
341+
342+ override predicate returnsDocumentQuery ( boolean asArray ) {
343+ MethodSignatures:: returnsDocumentQuery ( getMethodName ( ) , asArray )
306344 }
307- }
308345
309- /**
310- * A Mongoose query object as a result of a Document method call.
311- */
312- private class QueryFromDocument extends DataFlow:: MethodCallNode {
313- QueryFromDocument ( ) {
314- exists ( string name |
315- Document:: MethodSignatures:: returnsQuery ( name ) and
316- Document:: ref ( ) .getAMethodCall ( name ) = this
346+ override predicate interpretsArgumentAsQuery ( DataFlow:: Node arg ) {
347+ exists ( int n |
348+ MethodSignatures:: interpretsArgumentAsQuery ( this .getMethodName ( ) , n ) and
349+ arg = this .getArgument ( n )
317350 )
318351 }
319352 }
320353
321- /**
322- * A Mongoose query object as a result of a Query constructor invocation.
323- */
324- class QueryFromConstructor extends DataFlow:: NewNode {
325- QueryFromConstructor ( ) {
354+ private class NewQueryAsCommonInterface extends CommonInterface {
355+ NewQueryAsCommonInterface ( ) {
326356 this = getAMongooseInstance ( ) .getAPropertyRead ( "Query" ) .getAnInstantiation ( )
327357 }
358+
359+ override predicate returnsQuery ( ) { any ( ) }
360+
361+ override predicate returnsDocumentQuery ( boolean asArray ) { none ( ) }
362+
363+ override predicate interpretsArgumentAsQuery ( DataFlow:: Node arg ) { arg = this .getArgument ( 2 ) }
328364 }
329365
330366 /**
331367 * Gets a data flow node referring to a Mongoose query object.
332368 */
333369 private DataFlow:: SourceNode ref ( DataFlow:: TypeTracker t ) {
334370 (
335- result instanceof QueryFromConstructor or
336- result instanceof QueryFromModel or
337- result instanceof QueryFromDocument or
371+ result .( CommonInterface ) .returnsQuery ( ) or
338372 result .hasUnderlyingType ( "mongoose" , "Query" )
339373 ) and
340374 t .start ( )
@@ -475,10 +509,11 @@ private module Mongoose {
475509 }
476510
477511 /**
478- * Holds if Query method `name` returns one or more documents, the
479- * documents are wrapped in an array if `asArray` is true.
512+ * Holds if Query method `name` returns a query that results in
513+ * one or more documents, the documents are wrapped in an array
514+ * if `asArray` is true.
480515 */
481- predicate returnsDocument ( string name , boolean asArray ) {
516+ predicate returnsDocumentQuery ( string name , boolean asArray ) {
482517 asArray = false and name = "findOne"
483518 or
484519 asArray = true and name = "find"
@@ -490,20 +525,31 @@ private module Mongoose {
490525 * Provides classes modeling the Mongoose Document class
491526 */
492527 module Document {
528+ private class DocumentAsCommonInterface extends CommonInterface , DataFlow:: MethodCallNode {
529+ DocumentAsCommonInterface ( ) { this = ref ( ) .getAMethodCall ( ) }
530+
531+ override predicate returnsQuery ( ) { MethodSignatures:: returnsQuery ( getMethodName ( ) ) }
532+
533+ override predicate returnsDocumentQuery ( boolean asArray ) {
534+ MethodSignatures:: returnsDocumentQuery ( getMethodName ( ) , asArray )
535+ }
536+
537+ override predicate interpretsArgumentAsQuery ( DataFlow:: Node arg ) {
538+ exists ( int n |
539+ MethodSignatures:: interpretsArgumentAsQuery ( this .getMethodName ( ) , n ) and
540+ arg = this .getArgument ( n )
541+ )
542+ }
543+ }
544+
493545 /**
494546 * A Mongoose Document that is retrieved from the backing database.
495547 */
496548 class RetrievedDocument extends DataFlow:: SourceNode {
497549 RetrievedDocument ( ) {
498550 exists ( boolean asArray , DataFlow:: ParameterNode param |
499- exists ( DataFlow:: SourceNode base , DataFlow:: MethodCallNode call , string name |
500- base = Query:: ref ( ) and Query:: MethodSignatures:: returnsDocument ( name , asArray )
501- or
502- base = Model:: ref ( ) and Model:: MethodSignatures:: returnsDocument ( name , asArray )
503- or
504- base = ref ( ) and MethodSignatures:: returnsDocument ( name , asArray )
505- |
506- call = base .getAMethodCall ( name ) and
551+ exists ( CommonInterface call |
552+ call .returnsDocumentQuery ( asArray ) and
507553 param = call .getCallback ( call .getNumArgument ( ) - 1 ) .getParameter ( 1 )
508554 )
509555 or
@@ -520,7 +566,7 @@ private module Mongoose {
520566 param = call .getCallback ( 0 ) .getParameter ( paramIndex ) and
521567 exists ( DataFlow:: MethodCallNode pred |
522568 // limitation: look at the previous method call
523- Query:: MethodSignatures:: returnsDocument ( pred .getMethodName ( ) , asArray ) and
569+ Query:: MethodSignatures:: returnsDocumentQuery ( pred .getMethodName ( ) , asArray ) and
524570 pred .getAMethodCall ( ) = call
525571 )
526572 )
@@ -551,8 +597,7 @@ private module Mongoose {
551597 exists ( DataFlow:: TypeTracker t2 , DataFlow:: SourceNode succ | succ = ref ( t2 ) |
552598 result = succ .track ( t2 , t )
553599 or
554- result =
555- succ .getAMethodCall ( any ( string name | MethodSignatures:: returnsDocument ( name , true ) ) ) and
600+ result = succ .getAMethodCall ( any ( string name | MethodSignatures:: returnsDocument ( name ) ) ) and
556601 t = t2 .continue ( )
557602 )
558603 }
@@ -562,7 +607,7 @@ private module Mongoose {
562607 */
563608 DataFlow:: SourceNode ref ( ) { result = ref ( DataFlow:: TypeTracker:: end ( ) ) }
564609
565- module MethodSignatures {
610+ private module MethodSignatures {
566611 /**
567612 * Holds if Document method `name` returns a Query.
568613 */
@@ -590,20 +635,23 @@ private module Mongoose {
590635 }
591636
592637 /**
593- * Holds if Document method `name` returns one or more documents, the
594- * documents are wrapped in an array if `asArray` is true.
638+ * Holds if Document method `name` returns a query that results in
639+ * one or more documents, the documents are wrapped in an array
640+ * if `asArray` is true.
595641 */
596- predicate returnsDocument ( string name , boolean asArray ) {
642+ predicate returnsDocumentQuery ( string name , boolean asArray ) {
597643 // Documents are subtypes of Models
598- Model:: MethodSignatures:: returnsDocument ( name , asArray )
599- or
600- asArray = false and
601- (
602- name = "depopulate" or
603- name = "init" or
604- name = "populate" or
605- name = "overwrite"
606- )
644+ Model:: MethodSignatures:: returnsDocumentQuery ( name , asArray )
645+ }
646+
647+ /**
648+ * Holds if Document method `name` returns a Document.
649+ */
650+ predicate returnsDocument ( string name ) {
651+ name = "depopulate" or
652+ name = "init" or
653+ name = "populate" or
654+ name = "overwrite"
607655 }
608656 }
609657 }
@@ -629,66 +677,39 @@ private module Mongoose {
629677 * An expression that is interpreted as a (part of a) MongoDB query.
630678 */
631679 class MongoDBQueryPart extends NoSQL:: Query {
632- MongoDBQueryPart ( ) {
633- exists ( DataFlow:: MethodCallNode mcn , string method , int n |
634- Model:: MethodSignatures:: interpretsArgumentAsQuery ( method , n ) and
635- mcn = Model:: ref ( ) .getAMethodCall ( method ) and
636- this = mcn .getArgument ( n ) .asExpr ( )
637- )
638- or
639- this = any ( Query:: QueryFromConstructor c ) .getArgument ( 2 ) .asExpr ( )
640- or
641- exists ( string method , int n | Query:: MethodSignatures:: interpretsArgumentAsQuery ( method , n ) |
642- this = Query:: ref ( ) .getAMethodCall ( method ) .getArgument ( n ) .asExpr ( )
643- )
644- or
645- exists ( DataFlow:: MethodCallNode mcn , string method , int n |
646- Document:: MethodSignatures:: interpretsArgumentAsQuery ( method , n ) and
647- mcn = Document:: ref ( ) .getAMethodCall ( method ) and
648- this = mcn .getArgument ( n ) .asExpr ( )
649- )
650- }
680+ MongoDBQueryPart ( ) { any ( CommonInterface call ) .interpretsArgumentAsQuery ( this .flow ( ) ) }
651681 }
652682
653683 /**
654684 * An evaluation of a MongoDB query.
655685 */
656- class MongoDBQueryEvaluation extends DatabaseAccess {
657- DataFlow:: MethodCallNode mcn ;
686+ class ShorthandQueryEvaluation extends DatabaseAccess {
687+ CommonInterface common ;
688+
689+ ShorthandQueryEvaluation ( ) {
690+ this = common and
691+ // shorthand for execution: provide a callback
692+ common .returnsQuery ( ) and
693+ exists ( common .getCallback ( common .getNumArgument ( ) - 1 ) )
694+ }
658695
659- MongoDBQueryEvaluation ( ) {
660- this = mcn and
661- (
662- exists ( string method |
663- Model:: MethodSignatures:: returnsQuery ( method ) and
664- mcn = Model:: ref ( ) .getAMethodCall ( method ) and
665- // callback provided to a Model method call
666- exists ( mcn .getCallback ( mcn .getNumArgument ( ) - 1 ) )
667- )
668- or
669- Query:: ref ( ) .getAMethodCall ( ) = mcn and
670- (
671- // explicit execution using a Query method call
672- exists ( string executor | executor = "exec" or executor = "then" or executor = "catch" |
673- mcn .getMethodName ( ) = executor
674- )
675- or
676- // callback provided to a Query method call
677- exists ( mcn .getCallback ( mcn .getNumArgument ( ) - 1 ) )
678- )
679- or
680- exists ( string method |
681- Document:: MethodSignatures:: returnsQuery ( method ) and
682- mcn = Document:: ref ( ) .getAMethodCall ( method ) and
683- // callback provided to a Document method call
684- exists ( mcn .getCallback ( mcn .getNumArgument ( ) - 1 ) )
685- )
696+ override DataFlow:: Node getAQueryArgument ( ) {
697+ // NB: the complete information is not easily accessible for deeply chained calls
698+ common .interpretsArgumentAsQuery ( result )
699+ }
700+ }
701+
702+ class ExplicitQueryEvaluation extends DatabaseAccess {
703+ ExplicitQueryEvaluation ( ) {
704+ // explicit execution using a Query method call
705+ exists ( string executor | executor = "exec" or executor = "then" or executor = "catch" |
706+ Query:: ref ( ) .getAMethodCall ( executor ) = this
686707 )
687708 }
688709
689710 override DataFlow:: Node getAQueryArgument ( ) {
690- // NB: this does not account for all of the chained calls leading to this execution
691- mcn . getAnArgument ( ) . asExpr ( ) . ( MongoDBQueryPart ) . flow ( ) = result
711+ // NB: the complete information is not easily accessible for deeply chained calls
712+ none ( )
692713 }
693714 }
694715}
0 commit comments