mirror of
https://github.com/github/codeql.git
synced 2026-02-19 08:23:45 +01:00
ruby/python: implement shared module
ruby: - create new shared file `SummaryTypeTracker.qll` - move much logic into the module - instantiate the module - remove old logic, now provided by module python: - clone shared file - instantiate module - use (some of the) steps provided by the module
This commit is contained in:
382
ruby/ql/lib/codeql/ruby/typetracking/SummaryTypeTracker.qll
Normal file
382
ruby/ql/lib/codeql/ruby/typetracking/SummaryTypeTracker.qll
Normal file
@@ -0,0 +1,382 @@
|
||||
/**
|
||||
* Provides the implementation of a summary type tracker, that is type tracking through flow summaries.
|
||||
* To use this, you must implement the `Input` signature. You can then use the predicates in the `Output`
|
||||
* signature to implement the predicates of the same names inside `TypeTrackerSpecific.qll`.
|
||||
*/
|
||||
|
||||
/** The classes and predicates needed to generate a summary type tracker. */
|
||||
signature module Input {
|
||||
// Dataflow nodes
|
||||
class Node;
|
||||
|
||||
// Content
|
||||
class TypeTrackerContent;
|
||||
|
||||
class TypeTrackerContentFilter;
|
||||
|
||||
// Relating content and filters
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
TypeTrackerContentFilter getFilterFromWithoutContentStep(TypeTrackerContent content);
|
||||
|
||||
/**
|
||||
* Gets a content filter to use for a `WithContent[content]` step, or has no result if
|
||||
* the step cannot be handled by type-tracking.
|
||||
*
|
||||
* `WithContent` is often used to perform strong updates on individual collection elements (or rather
|
||||
* to preserve those that didn't get updated). But for type-tracking this is rarely beneficial and quite expensive.
|
||||
* However, `WithContent` can be quite useful for restricting the type of an object, and in these cases we translate it to a filter.
|
||||
*/
|
||||
TypeTrackerContentFilter getFilterFromWithContentStep(TypeTrackerContent content);
|
||||
|
||||
// Summaries and their stacks
|
||||
class SummaryComponent;
|
||||
|
||||
class SummaryComponentStack {
|
||||
SummaryComponent head();
|
||||
}
|
||||
|
||||
/** Gets a singleton stack containing `component`. */
|
||||
SummaryComponentStack singleton(SummaryComponent component);
|
||||
|
||||
/**
|
||||
* Gets the stack obtained by pushing `head` onto `tail`.
|
||||
*/
|
||||
SummaryComponentStack push(SummaryComponent component, SummaryComponentStack stack);
|
||||
|
||||
/** Gets a singleton stack representing a return. */
|
||||
SummaryComponent return();
|
||||
|
||||
// Relating content to summaries
|
||||
/** Gets a summary component for content `c`. */
|
||||
SummaryComponent content(TypeTrackerContent contents);
|
||||
|
||||
/** Gets a summary component where data is not allowed to be stored in `c`. */
|
||||
SummaryComponent withoutContent(TypeTrackerContent contents);
|
||||
|
||||
/** Gets a summary component where data must be stored in `c`. */
|
||||
SummaryComponent withContent(TypeTrackerContent contents);
|
||||
|
||||
// Callables
|
||||
class SummarizedCallable {
|
||||
predicate propagatesFlow(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
);
|
||||
}
|
||||
|
||||
// Relating nodes to summaries
|
||||
/** Gets a dataflow node respresenting the argument of `call` indicated by `arg`. */
|
||||
Node argumentOf(Node call, SummaryComponent arg);
|
||||
|
||||
/** Gets a dataflow node respresenting the parameter of `callable` indicated by `param`. */
|
||||
Node parameterOf(Node callable, SummaryComponent param);
|
||||
|
||||
/** Gets a dataflow node respresenting the return of `callable` indicated by `return`. */
|
||||
Node returnOf(Node callable, SummaryComponent return);
|
||||
|
||||
// Specific summary handling
|
||||
/** Holds if component should be treated as a level step by type tracking. */
|
||||
predicate componentLevelStep(SummaryComponent component);
|
||||
|
||||
/** Holds if the given component can't be evaluated by `evaluateSummaryComponentStackLocal`. */
|
||||
predicate isNonLocal(SummaryComponent component);
|
||||
|
||||
// Relating callables to nodes
|
||||
/** Gets a dataflow node respresenting a call to `callable`. */
|
||||
Node callTo(SummarizedCallable callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The predicates provided by a summary type tracker.
|
||||
* These are meant to be used in `TypeTrackerSpecific.qll`
|
||||
* inside the predicates of the same names.
|
||||
*/
|
||||
signature module Output<Input I> {
|
||||
/**
|
||||
* Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph.
|
||||
*/
|
||||
predicate levelStepNoCall(I::Node nodeFrom, I::Node nodeTo);
|
||||
|
||||
/**
|
||||
* Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
|
||||
*/
|
||||
predicate basicLoadStep(I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContent content);
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
|
||||
*/
|
||||
predicate basicStoreStep(I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContent content);
|
||||
|
||||
/**
|
||||
* Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`.
|
||||
*/
|
||||
predicate basicLoadStoreStep(
|
||||
I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContent loadContent,
|
||||
I::TypeTrackerContent storeContent
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here.
|
||||
*/
|
||||
predicate basicWithoutContentStep(
|
||||
I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContentFilter filter
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`.
|
||||
*/
|
||||
predicate basicWithContentStep(
|
||||
I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContentFilter filter
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the summary type tracker, that is type tracking through flow summaries.
|
||||
*/
|
||||
module SummaryFlow<Input I> implements Output<I> {
|
||||
pragma[nomagic]
|
||||
private predicate hasLoadSummary(
|
||||
I::SummarizedCallable callable, I::TypeTrackerContent contents, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
) {
|
||||
callable.propagatesFlow(I::push(I::content(contents), input), output, true) and
|
||||
not I::isNonLocal(input.head()) and
|
||||
not I::isNonLocal(output.head())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasStoreSummary(
|
||||
I::SummarizedCallable callable, I::TypeTrackerContent contents, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
) {
|
||||
not I::isNonLocal(input.head()) and
|
||||
not I::isNonLocal(output.head()) and
|
||||
(
|
||||
callable.propagatesFlow(input, I::push(I::content(contents), output), true)
|
||||
or
|
||||
// Allow the input to start with an arbitrary WithoutContent[X].
|
||||
// Since type-tracking only tracks one content deep, and we're about to store into another content,
|
||||
// we're already preventing the input from being in a content.
|
||||
callable
|
||||
.propagatesFlow(I::push(I::withoutContent(_), input),
|
||||
I::push(I::content(contents), output), true)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasLoadStoreSummary(
|
||||
I::SummarizedCallable callable, I::TypeTrackerContent loadContents,
|
||||
I::TypeTrackerContent storeContents, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
) {
|
||||
callable
|
||||
.propagatesFlow(I::push(I::content(loadContents), input),
|
||||
I::push(I::content(storeContents), output), true) and
|
||||
not I::isNonLocal(input.head()) and
|
||||
not I::isNonLocal(output.head())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasWithoutContentSummary(
|
||||
I::SummarizedCallable callable, I::TypeTrackerContentFilter filter,
|
||||
I::SummaryComponentStack input, I::SummaryComponentStack output
|
||||
) {
|
||||
exists(I::TypeTrackerContent content |
|
||||
callable.propagatesFlow(I::push(I::withoutContent(content), input), output, true) and
|
||||
filter = I::getFilterFromWithoutContentStep(content) and
|
||||
not I::isNonLocal(input.head()) and
|
||||
not I::isNonLocal(output.head()) and
|
||||
input != output
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasWithContentSummary(
|
||||
I::SummarizedCallable callable, I::TypeTrackerContentFilter filter,
|
||||
I::SummaryComponentStack input, I::SummaryComponentStack output
|
||||
) {
|
||||
exists(I::TypeTrackerContent content |
|
||||
callable.propagatesFlow(I::push(I::withContent(content), input), output, true) and
|
||||
filter = I::getFilterFromWithContentStep(content) and
|
||||
not I::isNonLocal(input.head()) and
|
||||
not I::isNonLocal(output.head()) and
|
||||
input != output
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow I::Node corresponding an argument or return value of `call`,
|
||||
* as specified by `component`.
|
||||
*/
|
||||
bindingset[call, component]
|
||||
private I::Node evaluateSummaryComponentLocal(I::Node call, I::SummaryComponent component) {
|
||||
result = I::argumentOf(call, component)
|
||||
or
|
||||
component = I::return() and
|
||||
result = call
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `callable` is relevant for type-tracking and we therefore want `stack` to
|
||||
* be evaluated locally at its call sites.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStack(
|
||||
I::SummarizedCallable callable, I::SummaryComponentStack stack
|
||||
) {
|
||||
exists(I::callTo(callable)) and
|
||||
(
|
||||
callable.propagatesFlow(stack, _, true)
|
||||
or
|
||||
callable.propagatesFlow(_, stack, true)
|
||||
or
|
||||
// include store summaries as they may skip an initial step at the input
|
||||
hasStoreSummary(callable, _, stack, _)
|
||||
)
|
||||
or
|
||||
dependsOnSummaryComponentStackCons(callable, _, stack)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackCons(
|
||||
I::SummarizedCallable callable, I::SummaryComponent head, I::SummaryComponentStack tail
|
||||
) {
|
||||
dependsOnSummaryComponentStack(callable, I::push(head, tail))
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackConsLocal(
|
||||
I::SummarizedCallable callable, I::SummaryComponent head, I::SummaryComponentStack tail
|
||||
) {
|
||||
dependsOnSummaryComponentStackCons(callable, head, tail) and
|
||||
not I::isNonLocal(head)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackLeaf(
|
||||
I::SummarizedCallable callable, I::SummaryComponent leaf
|
||||
) {
|
||||
dependsOnSummaryComponentStack(callable, I::singleton(leaf))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow I::Node corresponding to the local input or output of `call`
|
||||
* identified by `stack`, if possible.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private I::Node evaluateSummaryComponentStackLocal(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack stack
|
||||
) {
|
||||
exists(I::SummaryComponent component |
|
||||
dependsOnSummaryComponentStackLeaf(callable, component) and
|
||||
stack = I::singleton(component) and
|
||||
call = I::callTo(callable) and
|
||||
result = evaluateSummaryComponentLocal(call, component)
|
||||
)
|
||||
or
|
||||
exists(I::Node prev, I::SummaryComponent head, I::SummaryComponentStack tail |
|
||||
prev = evaluateSummaryComponentStackLocal(callable, call, tail) and
|
||||
dependsOnSummaryComponentStackConsLocal(callable, pragma[only_bind_into](head),
|
||||
pragma[only_bind_out](tail)) and
|
||||
stack = I::push(pragma[only_bind_out](head), pragma[only_bind_out](tail))
|
||||
|
|
||||
result = I::parameterOf(prev, head)
|
||||
or
|
||||
result = I::returnOf(prev, head)
|
||||
or
|
||||
I::componentLevelStep(head) and
|
||||
result = prev
|
||||
)
|
||||
}
|
||||
|
||||
// Implement Output
|
||||
predicate levelStepNoCall(I::Node nodeFrom, I::Node nodeTo) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
callable.propagatesFlow(input, output, true) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicLoadStep(I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContent content) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasLoadSummary(callable, content, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicStoreStep(I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContent content) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasStoreSummary(callable, content, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicLoadStoreStep(
|
||||
I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContent loadContent,
|
||||
I::TypeTrackerContent storeContent
|
||||
) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasLoadStoreSummary(callable, loadContent, storeContent, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicWithoutContentStep(
|
||||
I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContentFilter filter
|
||||
) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasWithoutContentSummary(callable, filter, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicWithContentStep(
|
||||
I::Node nodeFrom, I::Node nodeTo, I::TypeTrackerContentFilter filter
|
||||
) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasWithContentSummary(callable, filter, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -112,15 +112,7 @@ predicate levelStepCall(Node nodeFrom, Node nodeTo) {
|
||||
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */
|
||||
pragma[nomagic]
|
||||
predicate levelStepNoCall(Node nodeFrom, Node nodeTo) {
|
||||
exists(
|
||||
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponentStack input,
|
||||
SummaryComponentStack output
|
||||
|
|
||||
callable.propagatesFlow(input, output, true) and
|
||||
call.asExpr().getExpr() = callable.getACallSimple() and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output)
|
||||
)
|
||||
TypeTrackerSummaryFlow::levelStepNoCall(nodeFrom, nodeTo)
|
||||
or
|
||||
localFieldStep(nodeFrom, nodeTo)
|
||||
}
|
||||
@@ -290,16 +282,7 @@ predicate returnStep(Node nodeFrom, Node nodeTo) {
|
||||
predicate basicStoreStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) {
|
||||
storeStepIntoSourceNode(nodeFrom, nodeTo, contents)
|
||||
or
|
||||
exists(
|
||||
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponentStack input,
|
||||
SummaryComponentStack output
|
||||
|
|
||||
hasStoreSummary(callable, contents, 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)
|
||||
)
|
||||
TypeTrackerSummaryFlow::basicStoreStep(nodeFrom, nodeTo, contents)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,15 +316,7 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet content
|
||||
nodeTo.asExpr() = call
|
||||
)
|
||||
or
|
||||
exists(
|
||||
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponentStack input,
|
||||
SummaryComponentStack output
|
||||
|
|
||||
hasLoadSummary(callable, contents, 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)
|
||||
)
|
||||
TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, contents)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,48 +325,21 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet content
|
||||
predicate basicLoadStoreStep(
|
||||
Node nodeFrom, Node nodeTo, DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent
|
||||
) {
|
||||
exists(
|
||||
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponentStack input,
|
||||
SummaryComponentStack output
|
||||
|
|
||||
hasLoadStoreSummary(callable, loadContent, storeContent, 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)
|
||||
)
|
||||
TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
)
|
||||
TypeTrackerSummaryFlow::basicWithoutContentStep(nodeFrom, nodeTo, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
)
|
||||
TypeTrackerSummaryFlow::basicWithContentStep(nodeFrom, nodeTo, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,121 +351,6 @@ class Boolean extends boolean {
|
||||
|
||||
private import SummaryComponentStack
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasStoreSummary(
|
||||
SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponentStack input,
|
||||
SummaryComponentStack output
|
||||
) {
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head()) and
|
||||
(
|
||||
callable.propagatesFlow(input, push(SummaryComponent::content(contents), output), true)
|
||||
or
|
||||
// Allow the input to start with an arbitrary WithoutContent[X].
|
||||
// Since type-tracking only tracks one content deep, and we're about to store into another content,
|
||||
// we're already preventing the input from being in a content.
|
||||
callable
|
||||
.propagatesFlow(push(SummaryComponent::withoutContent(_), input),
|
||||
push(SummaryComponent::content(contents), output), true)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasLoadSummary(
|
||||
SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponentStack input,
|
||||
SummaryComponentStack output
|
||||
) {
|
||||
callable.propagatesFlow(push(SummaryComponent::content(contents), input), output, true) and
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasLoadStoreSummary(
|
||||
SummarizedCallable callable, DataFlow::ContentSet loadContents,
|
||||
DataFlow::ContentSet storeContents, SummaryComponentStack input, SummaryComponentStack output
|
||||
) {
|
||||
callable
|
||||
.propagatesFlow(push(SummaryComponent::content(loadContents), input),
|
||||
push(SummaryComponent::content(storeContents), output), true) and
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head())
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.isElementLowerBoundOrUnknown(_)
|
||||
or
|
||||
content.isElementOfTypeOrUnknown(_)
|
||||
or
|
||||
content.isSingleton(any(DataFlow::Content::UnknownElementContent c))
|
||||
) and
|
||||
result = MkElementFilter()
|
||||
}
|
||||
|
||||
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
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head()) and
|
||||
input != output
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a content filter to use for a `WithContent[content]` step, or has no result if
|
||||
* the step cannot be handled by type-tracking.
|
||||
*
|
||||
* `WithContent` is often used to perform strong updates on individual collection elements (or rather
|
||||
* to preserve those that didn't get updated). But for type-tracking this is rarely beneficial and quite expensive.
|
||||
* However, `WithContent` 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.isElementOfType(_)
|
||||
or
|
||||
content.isElementOfTypeOrUnknown(_)
|
||||
or
|
||||
content.isSingleton(any(DataFlow::Content::ElementContent c))
|
||||
) and
|
||||
result = MkElementFilter()
|
||||
}
|
||||
|
||||
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
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head()) and
|
||||
input != output
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given component can't be evaluated by `evaluateSummaryComponentStackLocal`.
|
||||
*/
|
||||
@@ -528,112 +361,129 @@ predicate isNonLocal(SummaryComponent component) {
|
||||
component = SC::withContent(_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node corresponding an argument or return value of `call`,
|
||||
* as specified by `component`.
|
||||
*/
|
||||
bindingset[call, component]
|
||||
private DataFlow::Node evaluateSummaryComponentLocal(
|
||||
DataFlow::CallNode call, SummaryComponent component
|
||||
) {
|
||||
exists(DataFlowDispatch::ParameterPosition pos |
|
||||
component = SummaryComponent::argument(pos) and
|
||||
argumentPositionMatch(call.asExpr(), result, pos)
|
||||
)
|
||||
or
|
||||
component = SummaryComponent::return() and
|
||||
result = call
|
||||
}
|
||||
private import SummaryTypeTracker as SummaryTypeTracker
|
||||
private import codeql.ruby.dataflow.FlowSummary as FlowSummary
|
||||
|
||||
/**
|
||||
* Holds if `callable` is relevant for type-tracking and we therefore want `stack` to
|
||||
* be evaluated locally at its call sites.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStack(
|
||||
SummarizedCallable callable, SummaryComponentStack stack
|
||||
) {
|
||||
exists(callable.getACallSimple()) and
|
||||
(
|
||||
callable.propagatesFlow(stack, _, true)
|
||||
or
|
||||
callable.propagatesFlow(_, stack, true)
|
||||
or
|
||||
// include store summaries as they may skip an initial step at the input
|
||||
hasStoreSummary(callable, _, stack, _)
|
||||
)
|
||||
or
|
||||
dependsOnSummaryComponentStackCons(callable, _, stack)
|
||||
}
|
||||
module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
|
||||
// Dataflow nodes
|
||||
class Node = DataFlow::Node;
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackCons(
|
||||
SummarizedCallable callable, SummaryComponent head, SummaryComponentStack tail
|
||||
) {
|
||||
dependsOnSummaryComponentStack(callable, SCS::push(head, tail))
|
||||
}
|
||||
// Content
|
||||
class TypeTrackerContent = DataFlowPublic::ContentSet;
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackConsLocal(
|
||||
SummarizedCallable callable, SummaryComponent head, SummaryComponentStack tail
|
||||
) {
|
||||
dependsOnSummaryComponentStackCons(callable, head, tail) and
|
||||
not isNonLocal(head)
|
||||
}
|
||||
class TypeTrackerContentFilter = ContentFilter;
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackLeaf(
|
||||
SummarizedCallable callable, SummaryComponent leaf
|
||||
) {
|
||||
dependsOnSummaryComponentStack(callable, SCS::singleton(leaf))
|
||||
}
|
||||
TypeTrackerContentFilter getFilterFromWithoutContentStep(TypeTrackerContent content) {
|
||||
(
|
||||
content.isAnyElement()
|
||||
or
|
||||
content.isElementLowerBoundOrUnknown(_)
|
||||
or
|
||||
content.isElementOfTypeOrUnknown(_)
|
||||
or
|
||||
content.isSingleton(any(DataFlow::Content::UnknownElementContent c))
|
||||
) and
|
||||
result = MkElementFilter()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node corresponding to the local input or output of `call`
|
||||
* identified by `stack`, if possible.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node evaluateSummaryComponentStackLocal(
|
||||
SummarizedCallable callable, DataFlow::CallNode call, SummaryComponentStack stack
|
||||
) {
|
||||
exists(SummaryComponent component |
|
||||
dependsOnSummaryComponentStackLeaf(callable, component) and
|
||||
stack = SCS::singleton(component) and
|
||||
call.asExpr().getExpr() = callable.getACallSimple() and
|
||||
result = evaluateSummaryComponentLocal(call, component)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node prev, SummaryComponent head, SummaryComponentStack tail |
|
||||
prev = evaluateSummaryComponentStackLocal(callable, call, tail) and
|
||||
dependsOnSummaryComponentStackConsLocal(callable, pragma[only_bind_into](head),
|
||||
pragma[only_bind_out](tail)) and
|
||||
stack = SCS::push(pragma[only_bind_out](head), pragma[only_bind_out](tail))
|
||||
|
|
||||
TypeTrackerContentFilter getFilterFromWithContentStep(TypeTrackerContent content) {
|
||||
(
|
||||
content.isAnyElement()
|
||||
or
|
||||
content.isElementLowerBound(_)
|
||||
or
|
||||
content.isElementLowerBoundOrUnknown(_)
|
||||
or
|
||||
content.isElementOfType(_)
|
||||
or
|
||||
content.isElementOfTypeOrUnknown(_)
|
||||
or
|
||||
content.isSingleton(any(DataFlow::Content::ElementContent c))
|
||||
) and
|
||||
result = MkElementFilter()
|
||||
}
|
||||
|
||||
// Summaries and their stacks
|
||||
class SummaryComponent = FlowSummary::SummaryComponent;
|
||||
|
||||
class SummaryComponentStack = FlowSummary::SummaryComponentStack;
|
||||
|
||||
SummaryComponentStack singleton(SummaryComponent component) {
|
||||
result = FlowSummary::SummaryComponentStack::singleton(component)
|
||||
}
|
||||
|
||||
SummaryComponentStack push(SummaryComponent component, SummaryComponentStack stack) {
|
||||
result = FlowSummary::SummaryComponentStack::push(component, stack)
|
||||
}
|
||||
|
||||
// Relating content to summaries
|
||||
SummaryComponent content(TypeTrackerContent contents) {
|
||||
result = FlowSummary::SummaryComponent::content(contents)
|
||||
}
|
||||
|
||||
SummaryComponent withoutContent(TypeTrackerContent contents) {
|
||||
result = FlowSummary::SummaryComponent::withoutContent(contents)
|
||||
}
|
||||
|
||||
SummaryComponent withContent(TypeTrackerContent contents) {
|
||||
result = FlowSummary::SummaryComponent::withContent(contents)
|
||||
}
|
||||
|
||||
SummaryComponent return() { result = FlowSummary::SummaryComponent::return() }
|
||||
|
||||
// Callables
|
||||
class SummarizedCallable = FlowSummary::SummarizedCallable;
|
||||
|
||||
// Relating nodes to summaries
|
||||
Node argumentOf(Node call, SummaryComponent arg) {
|
||||
exists(DataFlowDispatch::ParameterPosition pos |
|
||||
arg = SummaryComponent::argument(pos) and
|
||||
argumentPositionMatch(call.asExpr(), result, pos)
|
||||
)
|
||||
}
|
||||
|
||||
Node parameterOf(Node callable, SummaryComponent param) {
|
||||
exists(
|
||||
DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos,
|
||||
DataFlowPrivate::ParameterNodeImpl p
|
||||
|
|
||||
head = SummaryComponent::parameter(apos) and
|
||||
param = SummaryComponent::parameter(apos) and
|
||||
DataFlowDispatch::parameterMatch(ppos, apos) and
|
||||
p.isSourceParameterOf(prev.asExpr().getExpr(), ppos) and
|
||||
p.isSourceParameterOf(callable.asExpr().getExpr(), ppos) and
|
||||
// We need to include both `p` and the SSA definition for `p`, since in type-tracking
|
||||
// the step from `p` to the SSA definition is considered a call step.
|
||||
result =
|
||||
[p.(DataFlow::Node), DataFlowPrivate::LocalFlow::getParameterDefNode(p.getParameter())]
|
||||
)
|
||||
or
|
||||
}
|
||||
|
||||
Node returnOf(Node callable, SummaryComponent return) {
|
||||
exists(DataFlowPrivate::SynthReturnNode ret |
|
||||
head = SummaryComponent::return() and
|
||||
ret.getCfgScope() = prev.asExpr().getExpr() and
|
||||
return = SummaryComponent::return() and
|
||||
ret.getCfgScope() = callable.asExpr().getExpr() and
|
||||
// We need to include both `ret` and `ret.getAnInput()`, since in type-tracking
|
||||
// the step from `ret.getAnInput()` to `ret` is considered a return step.
|
||||
result = [ret.(DataFlow::Node), ret.getAnInput()]
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ContentSet content |
|
||||
head = SummaryComponent::withoutContent(content) and
|
||||
not exists(getFilterFromWithoutContentStep(content)) and
|
||||
result = prev
|
||||
}
|
||||
|
||||
// Specific summary handling
|
||||
predicate componentLevelStep(SummaryComponent component) {
|
||||
exists(TypeTrackerContent content |
|
||||
component = SummaryComponent::withoutContent(content) and
|
||||
not exists(getFilterFromWithoutContentStep(content))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate isNonLocal(SummaryComponent component) {
|
||||
component = SC::content(_)
|
||||
or
|
||||
component = SC::withContent(_)
|
||||
}
|
||||
|
||||
// Relating callables to nodes
|
||||
Node callTo(SummarizedCallable callable) { result.asExpr().getExpr() = callable.getACallSimple() }
|
||||
}
|
||||
|
||||
module TypeTrackerSummaryFlow = SummaryTypeTracker::SummaryFlow<SummaryTypeTrackerInput>;
|
||||
|
||||
Reference in New Issue
Block a user