@@ -209,16 +209,61 @@ private module Mongoose {
209209 result = getAMongooseInstance ( ) .getAMemberCall ( "createConnection" )
210210 }
211211
212+ /**
213+ * A Mongoose function invocation.
214+ */
215+ private class InvokeNode extends DataFlow:: InvokeNode {
216+ /**
217+ * Holds if this invocation returns an object of type `Query`.
218+ */
219+ abstract predicate returnsQuery ( ) ;
220+
221+ /**
222+ * Holds if this invocation returns a `Query` that evaluates to 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 invocation 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 ModelInvokeNode extends InvokeNode , DataFlow:: MethodCallNode {
239+ ModelInvokeNode ( ) { 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 */
219258 private DataFlow:: SourceNode ref ( DataFlow:: TypeTracker t ) {
220259 (
221- result = getAMongooseInstance ( ) .getAMemberCall ( "model" ) or
260+ result = getAMongooseInstance ( ) .getAMemberCall ( "model" )
261+ or
262+ exists ( DataFlow:: SourceNode conn | conn = createConnection ( ) |
263+ result = conn .getAMemberCall ( "model" ) or
264+ result = conn .getAPropertyRead ( "models" ) .getAPropertyRead ( )
265+ )
266+ or
222267 result .hasUnderlyingType ( "mongoose" , "Model" )
223268 ) and
224269 t .start ( )
@@ -271,41 +316,59 @@ private module Mongoose {
271316 name = "updateOne" or
272317 name = "where"
273318 }
319+
320+ /**
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.
324+ */
325+ predicate returnsDocumentQuery ( string name , boolean asArray ) {
326+ asArray = false and name = "findOne"
327+ or
328+ asArray = true and name = "find"
329+ }
274330 }
275331 }
276332
277333 /**
278334 * Provides classes modeling the Mongoose Query class
279335 */
280336 module Query {
281- /**
282- * A Mongoose query object as a result of a Model method call.
283- */
284- private class QueryFromModel extends DataFlow:: MethodCallNode {
285- QueryFromModel ( ) {
286- exists ( string name |
287- Model:: MethodSignatures:: returnsQuery ( name ) and
288- Model:: ref ( ) .getAMethodCall ( name ) = this
337+ private class QueryInvokeNode extends InvokeNode , DataFlow:: MethodCallNode {
338+ QueryInvokeNode ( ) { this = ref ( ) .getAMethodCall ( ) }
339+
340+ override predicate returnsQuery ( ) { MethodSignatures:: returnsQuery ( getMethodName ( ) ) }
341+
342+ override predicate returnsDocumentQuery ( boolean asArray ) {
343+ MethodSignatures:: returnsDocumentQuery ( getMethodName ( ) , asArray )
344+ }
345+
346+ override predicate interpretsArgumentAsQuery ( DataFlow:: Node arg ) {
347+ exists ( int n |
348+ MethodSignatures:: interpretsArgumentAsQuery ( this .getMethodName ( ) , n ) and
349+ arg = this .getArgument ( n )
289350 )
290351 }
291352 }
292353
293- /**
294- * A Mongoose query object as a result of a Query constructor invocation.
295- */
296- class QueryFromConstructor extends DataFlow:: NewNode {
297- QueryFromConstructor ( ) {
354+ private class NewQueryInvokeNode extends InvokeNode {
355+ NewQueryInvokeNode ( ) {
298356 this = getAMongooseInstance ( ) .getAPropertyRead ( "Query" ) .getAnInstantiation ( )
299357 }
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 ) }
300364 }
301365
302366 /**
303367 * Gets a data flow node referring to a Mongoose query object.
304368 */
305369 private DataFlow:: SourceNode ref ( DataFlow:: TypeTracker t ) {
306370 (
307- result instanceof QueryFromConstructor or
308- result instanceof QueryFromModel or
371+ result .( InvokeNode ) .returnsQuery ( ) or
309372 result .hasUnderlyingType ( "mongoose" , "Query" )
310373 ) and
311374 t .start ( )
@@ -444,6 +507,152 @@ private module Mongoose {
444507 name = "within" or
445508 name = "wtimeout"
446509 }
510+
511+ /**
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.
515+ */
516+ predicate returnsDocumentQuery ( string name , boolean asArray ) {
517+ asArray = false and name = "findOne"
518+ or
519+ asArray = true and name = "find"
520+ }
521+ }
522+ }
523+
524+ /**
525+ * Provides classes modeling the Mongoose Document class
526+ */
527+ module Document {
528+ private class DocumentInvokeNode extends InvokeNode , DataFlow:: MethodCallNode {
529+ DocumentInvokeNode ( ) { 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+
545+ /**
546+ * A Mongoose Document that is retrieved from the backing database.
547+ */
548+ class RetrievedDocument extends DataFlow:: SourceNode {
549+ RetrievedDocument ( ) {
550+ exists ( boolean asArray , DataFlow:: ParameterNode param |
551+ exists ( InvokeNode call |
552+ call .returnsDocumentQuery ( asArray ) and
553+ param = call .getCallback ( call .getNumArgument ( ) - 1 ) .getParameter ( 1 )
554+ )
555+ or
556+ exists (
557+ DataFlow:: SourceNode base , DataFlow:: MethodCallNode call , string executor ,
558+ int paramIndex
559+ |
560+ executor = "then" and paramIndex = 0
561+ or
562+ executor = "exec" and paramIndex = 1
563+ |
564+ base = Query:: ref ( ) and
565+ call = base .getAMethodCall ( executor ) and
566+ param = call .getCallback ( 0 ) .getParameter ( paramIndex ) and
567+ exists ( DataFlow:: MethodCallNode pred |
568+ // limitation: look at the previous method call
569+ Query:: MethodSignatures:: returnsDocumentQuery ( pred .getMethodName ( ) , asArray ) and
570+ pred .getAMethodCall ( ) = call
571+ )
572+ )
573+ |
574+ asArray = false and this = param
575+ or
576+ asArray = true and
577+ exists ( DataFlow:: PropRead access |
578+ // limitation: look for direct accesses
579+ access = param .getAPropertyRead ( ) and
580+ not exists ( access .getPropertyName ( ) ) and
581+ this = access
582+ )
583+ )
584+ }
585+ }
586+
587+ /**
588+ * Gets a data flow node referring to a Mongoose Document object.
589+ */
590+ private DataFlow:: SourceNode ref ( DataFlow:: TypeTracker t ) {
591+ (
592+ result instanceof RetrievedDocument or
593+ result .hasUnderlyingType ( "mongoose" , "Document" )
594+ ) and
595+ t .start ( )
596+ or
597+ exists ( DataFlow:: TypeTracker t2 , DataFlow:: SourceNode succ | succ = ref ( t2 ) |
598+ result = succ .track ( t2 , t )
599+ or
600+ result = succ .getAMethodCall ( any ( string name | MethodSignatures:: returnsDocument ( name ) ) ) and
601+ t = t2 .continue ( )
602+ )
603+ }
604+
605+ /**
606+ * Gets a data flow node referring to a Mongoose Document object.
607+ */
608+ DataFlow:: SourceNode ref ( ) { result = ref ( DataFlow:: TypeTracker:: end ( ) ) }
609+
610+ private module MethodSignatures {
611+ /**
612+ * Holds if Document method `name` returns a Query.
613+ */
614+ predicate returnsQuery ( string name ) {
615+ // Documents are subtypes of Models
616+ Model:: MethodSignatures:: returnsQuery ( name ) or
617+ name = "replaceOne" or
618+ name = "update" or
619+ name = "updateOne"
620+ }
621+
622+ /**
623+ * Holds if Document method `name` interprets parameter `n` as a query.
624+ */
625+ predicate interpretsArgumentAsQuery ( string name , int n ) {
626+ // Documents are subtypes of Models
627+ Model:: MethodSignatures:: interpretsArgumentAsQuery ( name , n )
628+ or
629+ n = 0 and
630+ (
631+ name = "replaceOne" or
632+ name = "update" or
633+ name = "updateOne"
634+ )
635+ }
636+
637+ /**
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.
641+ */
642+ predicate returnsDocumentQuery ( string name , boolean asArray ) {
643+ // Documents are subtypes of Models
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"
655+ }
447656 }
448657 }
449658
@@ -468,53 +677,39 @@ private module Mongoose {
468677 * An expression that is interpreted as a (part of a) MongoDB query.
469678 */
470679 class MongoDBQueryPart extends NoSQL:: Query {
471- MongoDBQueryPart ( ) {
472- exists ( DataFlow:: MethodCallNode mcn , string method , int n |
473- Model:: MethodSignatures:: interpretsArgumentAsQuery ( method , n ) and
474- mcn = Model:: ref ( ) .getAMethodCall ( method ) and
475- this = mcn .getArgument ( n ) .asExpr ( )
476- )
477- or
478- this = any ( Query:: QueryFromConstructor c ) .getArgument ( 2 ) .asExpr ( )
479- or
480- exists ( string method , int n | Query:: MethodSignatures:: interpretsArgumentAsQuery ( method , n ) |
481- this = Query:: ref ( ) .getAMethodCall ( method ) .getArgument ( n ) .asExpr ( )
482- )
483- }
680+ MongoDBQueryPart ( ) { any ( InvokeNode call ) .interpretsArgumentAsQuery ( this .flow ( ) ) }
484681 }
485682
486683 /**
487684 * An evaluation of a MongoDB query.
488685 */
489- class MongoDBQueryEvaluation extends DatabaseAccess {
490- DataFlow:: MethodCallNode mcn ;
686+ class ShorthandQueryEvaluation extends DatabaseAccess {
687+ InvokeNode invk ;
688+
689+ ShorthandQueryEvaluation ( ) {
690+ this = invk and
691+ // shorthand for execution: provide a callback
692+ invk .returnsQuery ( ) and
693+ exists ( invk .getCallback ( invk .getNumArgument ( ) - 1 ) )
694+ }
491695
492- MongoDBQueryEvaluation ( ) {
493- this = mcn and
494- (
495- exists ( string method |
496- Model:: MethodSignatures:: returnsQuery ( method ) and
497- mcn = Model:: ref ( ) .getAMethodCall ( method ) and
498- // callback provided to a Model method call
499- exists ( mcn .getCallback ( mcn .getNumArgument ( ) - 1 ) )
500- )
501- or
502- Query:: ref ( ) .getAMethodCall ( ) = mcn and
503- (
504- // explicit execution using a Query method call
505- exists ( string executor | executor = "exec" or executor = "then" or executor = "catch" |
506- mcn .getMethodName ( ) = executor
507- )
508- or
509- // callback provided to a Query method call
510- exists ( mcn .getCallback ( mcn .getNumArgument ( ) - 1 ) )
511- )
696+ override DataFlow:: Node getAQueryArgument ( ) {
697+ // NB: the complete information is not easily accessible for deeply chained calls
698+ invk .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
512707 )
513708 }
514709
515710 override DataFlow:: Node getAQueryArgument ( ) {
516- // NB: this does not account for all of the chained calls leading to this execution
517- mcn . getAnArgument ( ) . asExpr ( ) . ( MongoDBQueryPart ) . flow ( ) = result
711+ // NB: the complete information is not easily accessible for deeply chained calls
712+ none ( )
518713 }
519714 }
520715}
0 commit comments