Ruby: support WithoutContent steps in restricted cases

fixup ContentFilter

fixup basicWith(out)contentstep
This commit is contained in:
Asger F
2022-10-01 00:25:42 +02:00
parent 323abf45ca
commit bd11946aec
2 changed files with 162 additions and 0 deletions

View File

@@ -17,6 +17,8 @@ private module Cached {
LoadStoreStep(TypeTrackerContent load, TypeTrackerContent store) {
basicLoadStoreStep(_, _, load, store)
} or
WithContent(ContentFilter filter) { basicWithContentStep(_, _, filter) } or
WithoutContent(ContentFilter filter) { basicWithoutContentStep(_, _, filter) } or
JumpStep()
cached
@@ -64,6 +66,14 @@ private module Cached {
or
step = JumpStep() and
result = MkTypeTracker(false, currentContents)
or
exists(ContentFilter filter | result = tt |
step = WithContent(filter) and
currentContents = filter.getAMatchingContent()
or
step = WithoutContent(filter) and
not currentContents = filter.getAMatchingContent()
)
)
or
exists(TypeTrackerContent storeContents, boolean hasCall |
@@ -109,6 +119,14 @@ private module Cached {
or
step = JumpStep() and
result = MkTypeBackTracker(false, content)
or
exists(ContentFilter filter | result = tbt |
step = WithContent(filter) and
content = filter.getAMatchingContent()
or
step = WithoutContent(filter) and
not content = filter.getAMatchingContent()
)
)
or
exists(TypeTrackerContent loadContents, boolean hasReturn |
@@ -174,6 +192,14 @@ private module Cached {
flowsToLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent) and
summary = LoadStoreStep(loadContent, storeContent)
)
or
exists(ContentFilter filter |
basicWithContentStep(nodeFrom, nodeTo, filter) and
summary = WithContent(filter)
or
basicWithoutContentStep(nodeFrom, nodeTo, filter) and
summary = WithoutContent(filter)
)
}
cached

View File

@@ -34,6 +34,32 @@ class OptionalTypeTrackerContent extends DataFlowPrivate::TOptionalContentSet {
}
}
private newtype TContentFilter =
MkElementFilter() or
MkPairValueFilter()
/**
* A label to use for `WithContent` and `WithoutContent` steps, restricting
* which `ContentSet` may pass through.
*/
class ContentFilter extends TContentFilter {
/** Gets a string representation of this content filter. */
string toString() {
this = MkElementFilter() and result = "elements"
or
this = MkPairValueFilter() and result = "pair value"
}
/** Gets the content of a type-tracker that matches this filter. */
TypeTrackerContent getAMatchingContent() {
this = MkElementFilter() and
result.getAReadContent() instanceof DataFlow::Content::ElementContent
or
this = MkPairValueFilter() and
result.getAReadContent() instanceof DataFlow::Content::PairValueContent
}
}
/**
* Holds if a value stored with `storeContents` can be read back with `loadContents`.
*/
@@ -254,6 +280,38 @@ predicate basicLoadStoreStep(
)
}
/**
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here.
*/
predicate basicWithoutContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) {
exists(
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponentStack input,
SummaryComponentStack output
|
hasWithoutContentSummary(callable, filter, pragma[only_bind_into](input),
pragma[only_bind_into](output)) and
call.asExpr().getExpr() = callable.getACallSimple() and
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
)
}
/**
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`.
*/
predicate basicWithContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) {
exists(
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponentStack input,
SummaryComponentStack output
|
hasWithContentSummary(callable, filter, pragma[only_bind_into](input),
pragma[only_bind_into](output)) and
call.asExpr().getExpr() = callable.getACallSimple() and
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
)
}
/**
* A utility class that is equivalent to `boolean` but does not require type joining.
*/
@@ -289,6 +347,78 @@ private predicate hasLoadStoreSummary(
push(SummaryComponent::content(storeContents), output), true)
}
/**
* Gets a content filter to use for a `WithoutContent[content]` step, or has no result if
* the step should be treated as ordinary flow.
*
* `WithoutContent` is often used to perform strong updates on individual collection elements, but for
* type-tracking this is rarely beneficial and quite expensive. However, `WithoutContent` can be quite useful
* for restricting the type of an object, and in these cases we translate it to a filter.
*/
private ContentFilter getFilterFromWithoutContentStep(DataFlow::ContentSet content) {
(
content.isAnyElement()
or
content.isElementLowerBound(_)
or
content.isElementLowerBoundOrUnknown(_)
or
content.isSingleton(any(DataFlow::Content::UnknownElementContent c))
) and
result = MkElementFilter()
or
content.isSingleton(any(DataFlow::Content::UnknownPairValueContent c)) and
result = MkPairValueFilter()
}
pragma[nomagic]
private predicate hasWithoutContentSummary(
SummarizedCallable callable, ContentFilter filter, SummaryComponentStack input,
SummaryComponentStack output
) {
exists(DataFlow::ContentSet content |
callable.propagatesFlow(push(SummaryComponent::withoutContent(content), input), output, true) and
filter = getFilterFromWithoutContentStep(content) and
input != output
)
}
/**
* Gets a content filter to use for a `WithoutContent[content]` step, or has no result if
* the step should be treated as ordinary flow.
*
* `WithoutContent` is often used to perform strong updates on individual collection elements, but for
* type-tracking this is rarely beneficial and quite expensive. However, `WithoutContent` can be quite useful
* for restricting the type of an object, and in these cases we translate it to a filter.
*/
private ContentFilter getFilterFromWithContentStep(DataFlow::ContentSet content) {
(
content.isAnyElement()
or
content.isElementLowerBound(_)
or
content.isElementLowerBoundOrUnknown(_)
or
content.isSingleton(any(DataFlow::Content::ElementContent c))
) and
result = MkElementFilter()
or
content.isSingleton(any(DataFlow::Content::PairValueContent c)) and
result = MkPairValueFilter()
}
pragma[nomagic]
private predicate hasWithContentSummary(
SummarizedCallable callable, ContentFilter filter, SummaryComponentStack input,
SummaryComponentStack output
) {
exists(DataFlow::ContentSet content |
callable.propagatesFlow(push(SummaryComponent::withContent(content), input), output, true) and
filter = getFilterFromWithContentStep(content) and
input != output
)
}
/**
* Gets a data flow node corresponding an argument or return value of `call`,
* as specified by `component`.
@@ -367,5 +497,11 @@ private DataFlow::Node evaluateSummaryComponentStackLocal(
or
head = SummaryComponent::return() and
result.(DataFlowPrivate::SynthReturnNode).getCfgScope() = prev.asExpr().getExpr()
or
exists(DataFlow::ContentSet content |
head = SummaryComponent::withoutContent(content) and
not exists(getFilterFromWithoutContentStep(content)) and
result = prev
)
)
}