@@ -34,6 +34,32 @@ class OptionalTypeTrackerContent extends DataFlowPrivate::TOptionalContentSet {
34
34
}
35
35
}
36
36
37
+ private newtype TContentFilter =
38
+ MkElementFilter ( ) or
39
+ MkPairValueFilter ( )
40
+
41
+ /**
42
+ * A label to use for `WithContent` and `WithoutContent` steps, restricting
43
+ * which `ContentSet` may pass through.
44
+ */
45
+ class ContentFilter extends TContentFilter {
46
+ /** Gets a string representation of this content filter. */
47
+ string toString ( ) {
48
+ this = MkElementFilter ( ) and result = "elements"
49
+ or
50
+ this = MkPairValueFilter ( ) and result = "pair value"
51
+ }
52
+
53
+ /** Gets the content of a type-tracker that matches this filter. */
54
+ TypeTrackerContent getAMatchingContent ( ) {
55
+ this = MkElementFilter ( ) and
56
+ result .getAReadContent ( ) instanceof DataFlow:: Content:: ElementContent
57
+ or
58
+ this = MkPairValueFilter ( ) and
59
+ result .getAReadContent ( ) instanceof DataFlow:: Content:: PairValueContent
60
+ }
61
+ }
62
+
37
63
/**
38
64
* Holds if a value stored with `storeContents` can be read back with `loadContents`.
39
65
*/
@@ -254,6 +280,38 @@ predicate basicLoadStoreStep(
254
280
)
255
281
}
256
282
283
+ /**
284
+ * Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here.
285
+ */
286
+ predicate basicWithoutContentStep ( Node nodeFrom , Node nodeTo , ContentFilter filter ) {
287
+ exists (
288
+ SummarizedCallable callable , DataFlowPublic:: CallNode call , SummaryComponentStack input ,
289
+ SummaryComponentStack output
290
+ |
291
+ hasWithoutContentSummary ( callable , filter , pragma [ only_bind_into ] ( input ) ,
292
+ pragma [ only_bind_into ] ( output ) ) and
293
+ call .asExpr ( ) .getExpr ( ) = callable .getACallSimple ( ) and
294
+ nodeFrom = evaluateSummaryComponentStackLocal ( callable , call , input ) and
295
+ nodeTo = evaluateSummaryComponentStackLocal ( callable , call , output )
296
+ )
297
+ }
298
+
299
+ /**
300
+ * Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`.
301
+ */
302
+ predicate basicWithContentStep ( Node nodeFrom , Node nodeTo , ContentFilter filter ) {
303
+ exists (
304
+ SummarizedCallable callable , DataFlowPublic:: CallNode call , SummaryComponentStack input ,
305
+ SummaryComponentStack output
306
+ |
307
+ hasWithContentSummary ( callable , filter , pragma [ only_bind_into ] ( input ) ,
308
+ pragma [ only_bind_into ] ( output ) ) and
309
+ call .asExpr ( ) .getExpr ( ) = callable .getACallSimple ( ) and
310
+ nodeFrom = evaluateSummaryComponentStackLocal ( callable , call , input ) and
311
+ nodeTo = evaluateSummaryComponentStackLocal ( callable , call , output )
312
+ )
313
+ }
314
+
257
315
/**
258
316
* A utility class that is equivalent to `boolean` but does not require type joining.
259
317
*/
@@ -289,6 +347,78 @@ private predicate hasLoadStoreSummary(
289
347
push ( SummaryComponent:: content ( storeContents ) , output ) , true )
290
348
}
291
349
350
+ /**
351
+ * Gets a content filter to use for a `WithoutContent[content]` step, or has no result if
352
+ * the step should be treated as ordinary flow.
353
+ *
354
+ * `WithoutContent` is often used to perform strong updates on individual collection elements, but for
355
+ * type-tracking this is rarely beneficial and quite expensive. However, `WithoutContent` can be quite useful
356
+ * for restricting the type of an object, and in these cases we translate it to a filter.
357
+ */
358
+ private ContentFilter getFilterFromWithoutContentStep ( DataFlow:: ContentSet content ) {
359
+ (
360
+ content .isAnyElement ( )
361
+ or
362
+ content .isElementLowerBound ( _)
363
+ or
364
+ content .isElementLowerBoundOrUnknown ( _)
365
+ or
366
+ content .isSingleton ( any ( DataFlow:: Content:: UnknownElementContent c ) )
367
+ ) and
368
+ result = MkElementFilter ( )
369
+ or
370
+ content .isSingleton ( any ( DataFlow:: Content:: UnknownPairValueContent c ) ) and
371
+ result = MkPairValueFilter ( )
372
+ }
373
+
374
+ pragma [ nomagic]
375
+ private predicate hasWithoutContentSummary (
376
+ SummarizedCallable callable , ContentFilter filter , SummaryComponentStack input ,
377
+ SummaryComponentStack output
378
+ ) {
379
+ exists ( DataFlow:: ContentSet content |
380
+ callable .propagatesFlow ( push ( SummaryComponent:: withoutContent ( content ) , input ) , output , true ) and
381
+ filter = getFilterFromWithoutContentStep ( content ) and
382
+ input != output
383
+ )
384
+ }
385
+
386
+ /**
387
+ * Gets a content filter to use for a `WithoutContent[content]` step, or has no result if
388
+ * the step should be treated as ordinary flow.
389
+ *
390
+ * `WithoutContent` is often used to perform strong updates on individual collection elements, but for
391
+ * type-tracking this is rarely beneficial and quite expensive. However, `WithoutContent` can be quite useful
392
+ * for restricting the type of an object, and in these cases we translate it to a filter.
393
+ */
394
+ private ContentFilter getFilterFromWithContentStep ( DataFlow:: ContentSet content ) {
395
+ (
396
+ content .isAnyElement ( )
397
+ or
398
+ content .isElementLowerBound ( _)
399
+ or
400
+ content .isElementLowerBoundOrUnknown ( _)
401
+ or
402
+ content .isSingleton ( any ( DataFlow:: Content:: ElementContent c ) )
403
+ ) and
404
+ result = MkElementFilter ( )
405
+ or
406
+ content .isSingleton ( any ( DataFlow:: Content:: PairValueContent c ) ) and
407
+ result = MkPairValueFilter ( )
408
+ }
409
+
410
+ pragma [ nomagic]
411
+ private predicate hasWithContentSummary (
412
+ SummarizedCallable callable , ContentFilter filter , SummaryComponentStack input ,
413
+ SummaryComponentStack output
414
+ ) {
415
+ exists ( DataFlow:: ContentSet content |
416
+ callable .propagatesFlow ( push ( SummaryComponent:: withContent ( content ) , input ) , output , true ) and
417
+ filter = getFilterFromWithContentStep ( content ) and
418
+ input != output
419
+ )
420
+ }
421
+
292
422
/**
293
423
* Gets a data flow node corresponding an argument or return value of `call`,
294
424
* as specified by `component`.
@@ -367,5 +497,11 @@ private DataFlow::Node evaluateSummaryComponentStackLocal(
367
497
or
368
498
head = SummaryComponent:: return ( ) and
369
499
result .( DataFlowPrivate:: SynthReturnNode ) .getCfgScope ( ) = prev .asExpr ( ) .getExpr ( )
500
+ or
501
+ exists ( DataFlow:: ContentSet content |
502
+ head = SummaryComponent:: withoutContent ( content ) and
503
+ not exists ( getFilterFromWithoutContentStep ( content ) ) and
504
+ result = prev
505
+ )
370
506
)
371
507
}
0 commit comments