mirror of
https://github.com/github/codeql.git
synced 2025-12-20 10:46:30 +01:00
Python: Added recursion guard
to ensure that the call graph seen by type tracking does not include summary calls resolved by type tracking. (I tried inserting a similar test into the Ruby codebase, and it still compiled) To get this to compile, I had to move the resolution of summary calls out of the data flow nodes and into the `viableCallable` predicate. This means that we now have a potential summary call for each cfg call node. (I tried using the base class, `DataFlowCall`, for this but calls to `map` got identified as class calls and would no longer be associated with a summary.) It is possible that the "NonLIbrary"-layers the were inserted into the hierarchy can be removed again.
This commit is contained in:
@@ -636,16 +636,22 @@ class SpecialCall extends DataFlowSourceCall, TSpecialCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A call to a summarized callable. */
|
/**
|
||||||
|
* A call to a summarized callable, a `LibraryCallable`.
|
||||||
|
*
|
||||||
|
* This is a potential call, really. It has an empty charpred, so any `CallNode` is allowed.
|
||||||
|
* It is here to stake out territory, as otherwise a call to `map` would be considered a `ClassCall`
|
||||||
|
* and not be available for a summary.
|
||||||
|
*/
|
||||||
class LibraryCall extends NormalCall {
|
class LibraryCall extends NormalCall {
|
||||||
LibraryCallable callable;
|
|
||||||
|
|
||||||
LibraryCall() { call = callable.getACall() }
|
|
||||||
|
|
||||||
// TODO: Implement Python calling convention?
|
// TODO: Implement Python calling convention?
|
||||||
override Node getArg(int n) { result = TCfgNode(call.getArg(n)) }
|
override Node getArg(int n) { result = TCfgNode(call.getArg(n)) }
|
||||||
|
|
||||||
override DataFlowCallable getCallable() { result.asLibraryCallable() = callable }
|
// We cannot refer to a `LibraryCallable` here,
|
||||||
|
// as that could in turn refer to type tracking.
|
||||||
|
// This call will be tied to a `LibraryCallable` via
|
||||||
|
// `getViableCallabe` when the global data flow is assembled.
|
||||||
|
override DataFlowCallable getCallable() { none() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -747,7 +753,18 @@ private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a viable run-time target for the call `call`. */
|
/** Gets a viable run-time target for the call `call`. */
|
||||||
DataFlowCallable viableCallable(DataFlowSourceCall call) { result = call.getCallable() }
|
DataFlowCallable viableCallable(DataFlowSourceCall call) {
|
||||||
|
result = call.getCallable()
|
||||||
|
or
|
||||||
|
// A call to a library callable with a flow summary
|
||||||
|
// In this situation we can not resolve the callable from the call,
|
||||||
|
// as that would make data flow depend on type tracking.
|
||||||
|
// Instead we reolve the call from the summary.
|
||||||
|
exists(LibraryCallable callable |
|
||||||
|
result = TLibraryCallable(callable) and
|
||||||
|
call.getNode() = callable.getACall()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private newtype TReturnKind = TNormalReturnKind()
|
private newtype TReturnKind = TNormalReturnKind()
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,13 @@ private DataFlowPrivate::DataFlowCallable getCallableForArgument(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. */
|
/**
|
||||||
|
* Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call.
|
||||||
|
*
|
||||||
|
* Flow into summarized library methods is not included, as that will lead to negative
|
||||||
|
* recursion (or, at best, terrible performance), since identifying calls to library
|
||||||
|
* methods is done using API graphs (which uses type tracking).
|
||||||
|
*/
|
||||||
predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPrivate::ParameterNodeImpl nodeTo) {
|
predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPrivate::ParameterNodeImpl nodeTo) {
|
||||||
// TODO: Support special methods?
|
// TODO: Support special methods?
|
||||||
exists(DataFlowPrivate::DataFlowCallable callable, int i |
|
exists(DataFlowPrivate::DataFlowCallable callable, int i |
|
||||||
|
|||||||
@@ -2,6 +2,25 @@ private import python
|
|||||||
private import semmle.python.dataflow.new.FlowSummary
|
private import semmle.python.dataflow.new.FlowSummary
|
||||||
private import semmle.python.ApiGraphs
|
private import semmle.python.ApiGraphs
|
||||||
|
|
||||||
|
/** This module ensures that the `callStep` predicate in
|
||||||
|
* our type tracker implelemtation does not refer to the
|
||||||
|
* `getACall` predicate on `SummarizedCallable`.
|
||||||
|
*/
|
||||||
|
module RecursionGuard {
|
||||||
|
private import semmle.python.dataflow.new.internal.TypeTrackerSpecific as TT
|
||||||
|
|
||||||
|
private class RecursionGuard extends SummarizedCallable {
|
||||||
|
RecursionGuard() { this = "RecursionGuard" }
|
||||||
|
|
||||||
|
override CallNode getACall() {
|
||||||
|
result.getFunction().(NameNode).getId() = this and
|
||||||
|
(TT::callStep(_, _) implies any())
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class SummarizedCallableIdentity extends SummarizedCallable {
|
private class SummarizedCallableIdentity extends SummarizedCallable {
|
||||||
SummarizedCallableIdentity() { this = "identity" }
|
SummarizedCallableIdentity() { this = "identity" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user