@@ -209,16 +209,61 @@ private module Mongoose {
209
209
result = getAMongooseInstance ( ) .getAMemberCall ( "createConnection" )
210
210
}
211
211
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
+
212
234
/**
213
235
* Provides classes modeling the Mongoose Model class
214
236
*/
215
237
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
+
216
255
/**
217
256
* Gets a data flow node referring to a Mongoose Model object.
218
257
*/
219
258
private DataFlow:: SourceNode ref ( DataFlow:: TypeTracker t ) {
220
259
(
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
222
267
result .hasUnderlyingType ( "mongoose" , "Model" )
223
268
) and
224
269
t .start ( )
@@ -271,41 +316,59 @@ private module Mongoose {
271
316
name = "updateOne" or
272
317
name = "where"
273
318
}
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
+ }
274
330
}
275
331
}
276
332
277
333
/**
278
334
* Provides classes modeling the Mongoose Query class
279
335
*/
280
336
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 )
289
350
)
290
351
}
291
352
}
292
353
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 ( ) {
298
356
this = getAMongooseInstance ( ) .getAPropertyRead ( "Query" ) .getAnInstantiation ( )
299
357
}
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 ) }
300
364
}
301
365
302
366
/**
303
367
* Gets a data flow node referring to a Mongoose query object.
304
368
*/
305
369
private DataFlow:: SourceNode ref ( DataFlow:: TypeTracker t ) {
306
370
(
307
- result instanceof QueryFromConstructor or
308
- result instanceof QueryFromModel or
371
+ result .( InvokeNode ) .returnsQuery ( ) or
309
372
result .hasUnderlyingType ( "mongoose" , "Query" )
310
373
) and
311
374
t .start ( )
@@ -444,6 +507,152 @@ private module Mongoose {
444
507
name = "within" or
445
508
name = "wtimeout"
446
509
}
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
+ }
447
656
}
448
657
}
449
658
@@ -468,53 +677,39 @@ private module Mongoose {
468
677
* An expression that is interpreted as a (part of a) MongoDB query.
469
678
*/
470
679
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 ( ) ) }
484
681
}
485
682
486
683
/**
487
684
* An evaluation of a MongoDB query.
488
685
*/
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
+ }
491
695
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
512
707
)
513
708
}
514
709
515
710
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 ( )
518
713
}
519
714
}
520
715
}
0 commit comments