mirror of
https://github.com/github/codeql.git
synced 2025-12-20 18:56:32 +01:00
Python: Add call-graph compare meta-queries
Also changed the definition of a relevant call-target, so it's only what is in the actual source code, which is what we want in the future! (so what we're designing type-tracking to handle) I also changed terminology from `callee` to `target`. It felt more natural this way in my opinion.
This commit is contained in:
@@ -1,16 +1,55 @@
|
||||
/**
|
||||
* Provides predicates for measuring the quality of the call graph, that is,
|
||||
* the number of calls that could be resolved to a callee.
|
||||
* the number of calls that could be resolved to a target.
|
||||
*/
|
||||
|
||||
import python
|
||||
import meta.MetaMetrics
|
||||
|
||||
newtype TTarget =
|
||||
TFunction(Function func) or
|
||||
TClass(Class cls)
|
||||
|
||||
class Target extends TTarget {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
|
||||
/** Gets the location of this dataflow call. */
|
||||
abstract Location getLocation();
|
||||
|
||||
/** Whether this target is relevant. */
|
||||
predicate isRelevant() { exists(this.getLocation().getFile().getRelativePath()) }
|
||||
}
|
||||
|
||||
class TargetFunction extends Target, TFunction {
|
||||
Function func;
|
||||
|
||||
TargetFunction() { this = TFunction(func) }
|
||||
|
||||
override string toString() { result = func.toString() }
|
||||
|
||||
override Location getLocation() { result = func.getLocation() }
|
||||
|
||||
Function getFunction() { result = func }
|
||||
}
|
||||
|
||||
class TargetClass extends Target, TClass {
|
||||
Class cls;
|
||||
|
||||
TargetClass() { this = TClass(cls) }
|
||||
|
||||
override string toString() { result = cls.toString() }
|
||||
|
||||
override Location getLocation() { result = cls.getLocation() }
|
||||
|
||||
Class getClass() { result = cls }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that is (possibly) relevant for analysis quality.
|
||||
* See `IgnoredFile` for details on what is excluded.
|
||||
*/
|
||||
class RelevantCall extends Call {
|
||||
class RelevantCall extends CallNode {
|
||||
RelevantCall() { not this.getLocation().getFile() instanceof IgnoredFile }
|
||||
}
|
||||
|
||||
@@ -18,12 +57,16 @@ class RelevantCall extends Call {
|
||||
module PointsToBasedCallGraph {
|
||||
/** A call that can be resolved by points-to. */
|
||||
class ResolvableCall extends RelevantCall {
|
||||
Value callee;
|
||||
Value targetValue;
|
||||
|
||||
ResolvableCall() { callee.getACall() = this.getAFlowNode() }
|
||||
ResolvableCall() { targetValue.getACall() = this }
|
||||
|
||||
/** Gets a resolved callee of this call. */
|
||||
Value getCallee() { result = callee }
|
||||
/** Gets a resolved target of this call. */
|
||||
Target getTarget() {
|
||||
result.(TargetFunction).getFunction() = targetValue.(CallableValue).getScope()
|
||||
or
|
||||
result.(TargetClass).getClass() = targetValue.(ClassValue).getScope()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that cannot be resolved by points-to. */
|
||||
@@ -32,34 +75,68 @@ module PointsToBasedCallGraph {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that can be resolved by points-to, where the resolved callee is relevant.
|
||||
* Relevant callees include:
|
||||
* - builtins
|
||||
* - standard library
|
||||
* A call that can be resolved by points-to, where the resolved target is relevant.
|
||||
* Relevant targets include:
|
||||
* - source code of the project
|
||||
*/
|
||||
class ResolvableCallRelevantCallee extends ResolvableCall {
|
||||
ResolvableCallRelevantCallee() {
|
||||
callee.isBuiltin()
|
||||
or
|
||||
exists(File file |
|
||||
file = callee.(CallableValue).getScope().getLocation().getFile()
|
||||
or
|
||||
file = callee.(ClassValue).getScope().getLocation().getFile()
|
||||
|
|
||||
file.inStdlib()
|
||||
or
|
||||
// part of the source code of the project
|
||||
exists(file.getRelativePath())
|
||||
class ResolvableCallRelevantTarget extends ResolvableCall {
|
||||
ResolvableCallRelevantTarget() {
|
||||
exists(Target target | target = getTarget() |
|
||||
exists(target.getLocation().getFile().getRelativePath())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that can be resolved by points-to, where the resolved callee is not considered relevant.
|
||||
* See `ResolvableCallRelevantCallee` for the definition of relevance.
|
||||
* A call that can be resolved by points-to, where the resolved target is not considered relevant.
|
||||
* See `ResolvableCallRelevantTarget` for the definition of relevance.
|
||||
*/
|
||||
class ResolvableCallIrrelevantCallee extends ResolvableCall {
|
||||
ResolvableCallIrrelevantCallee() { not this instanceof ResolvableCallRelevantCallee }
|
||||
class ResolvableCallIrrelevantTarget extends ResolvableCall {
|
||||
ResolvableCallIrrelevantTarget() { not this instanceof ResolvableCallRelevantTarget }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for call-graph resolution by using type-tracking. */
|
||||
module TypeTrackingBasedCallGraph {
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch as TT
|
||||
|
||||
/** A call that can be resolved by type-tracking. */
|
||||
class ResolvableCall extends RelevantCall {
|
||||
TT::DataFlowCallable dataflowTarget;
|
||||
|
||||
ResolvableCall() { dataflowTarget = TT::viableCallable(TT::TNormalCall(this)) }
|
||||
|
||||
/** Gets a resolved target of this call. */
|
||||
Target getTarget() {
|
||||
result.(TargetFunction).getFunction() = dataflowTarget.(TT::DataFlowFunction).getScope()
|
||||
// TODO: class calls
|
||||
// result.(TargetClass).getClass()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that cannot be resolved by type-tracking. */
|
||||
class UnresolvableCall extends RelevantCall {
|
||||
UnresolvableCall() { not this instanceof ResolvableCall }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that can be resolved by type-tracking, where the resolved callee is relevant.
|
||||
* Relevant targets include:
|
||||
* - source code of the project
|
||||
*/
|
||||
class ResolvableCallRelevantTarget extends ResolvableCall {
|
||||
ResolvableCallRelevantTarget() {
|
||||
exists(Target target | target = getTarget() |
|
||||
exists(target.getLocation().getFile().getRelativePath())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that can be resolved by type-tracking, where the resolved target is not considered relevant.
|
||||
* See `ResolvableCallRelevantTarget` for the definition of relevance.
|
||||
*/
|
||||
class ResolvableCallIrrelevantTarget extends ResolvableCall {
|
||||
ResolvableCallIrrelevantTarget() { not this instanceof ResolvableCallRelevantTarget }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
import python
|
||||
import CallGraphQuality
|
||||
|
||||
select projectRoot(), count(PointsToBasedCallGraph::ResolvableCallRelevantCallee call)
|
||||
select projectRoot(), count(PointsToBasedCallGraph::ResolvableCallRelevantTarget call)
|
||||
|
||||
17
python/ql/src/meta/analysis-quality/TTCallGraph.ql
Normal file
17
python/ql/src/meta/analysis-quality/TTCallGraph.ql
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @name New call graph edge from using type-tracking instead of points-to
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id py/meta/call-graph-new
|
||||
* @tags meta
|
||||
* @precision very-low
|
||||
*/
|
||||
|
||||
import python
|
||||
import CallGraphQuality
|
||||
|
||||
from CallNode call, Target target
|
||||
where
|
||||
target.isRelevant() and
|
||||
call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target
|
||||
select call, "$@ to $@", call, "Call", target, target.toString()
|
||||
18
python/ql/src/meta/analysis-quality/TTCallGraphMissing.ql
Normal file
18
python/ql/src/meta/analysis-quality/TTCallGraphMissing.ql
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @name Missing call graph edge from using type-tracking instead of points-to
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id py/meta/call-graph-missing
|
||||
* @tags meta
|
||||
* @precision very-low
|
||||
*/
|
||||
|
||||
import python
|
||||
import CallGraphQuality
|
||||
|
||||
from CallNode call, Target target
|
||||
where
|
||||
target.isRelevant() and
|
||||
call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and
|
||||
not call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target
|
||||
select call, "MISSING: $@ to $@", call, "Call", target, target.toString()
|
||||
18
python/ql/src/meta/analysis-quality/TTCallGraphNew.ql
Normal file
18
python/ql/src/meta/analysis-quality/TTCallGraphNew.ql
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @name New call graph edge from using type-tracking instead of points-to
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id py/meta/call-graph-new
|
||||
* @tags meta
|
||||
* @precision very-low
|
||||
*/
|
||||
|
||||
import python
|
||||
import CallGraphQuality
|
||||
|
||||
from CallNode call, Target target
|
||||
where
|
||||
target.isRelevant() and
|
||||
not call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and
|
||||
call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target
|
||||
select call, "NEW: $@ to $@", call, "Call", target, target.toString()
|
||||
35
python/ql/src/meta/analysis-quality/TTCallGraphOverview.ql
Normal file
35
python/ql/src/meta/analysis-quality/TTCallGraphOverview.ql
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @name Call graph edge overview from using type-tracking instead of points-to
|
||||
* @id py/meta/call-graph-overview
|
||||
* @precision very-low
|
||||
*/
|
||||
|
||||
import python
|
||||
import CallGraphQuality
|
||||
|
||||
from string tag, int c
|
||||
where
|
||||
tag = "SHARED" and
|
||||
c =
|
||||
count(CallNode call, Target target |
|
||||
target.isRelevant() and
|
||||
call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and
|
||||
call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target
|
||||
)
|
||||
or
|
||||
tag = "NEW" and
|
||||
c =
|
||||
count(CallNode call, Target target |
|
||||
target.isRelevant() and
|
||||
not call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and
|
||||
call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target
|
||||
)
|
||||
or
|
||||
tag = "MISSING" and
|
||||
c =
|
||||
count(CallNode call, Target target |
|
||||
target.isRelevant() and
|
||||
call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and
|
||||
not call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target
|
||||
)
|
||||
select tag, c
|
||||
18
python/ql/src/meta/analysis-quality/TTCallGraphShared.ql
Normal file
18
python/ql/src/meta/analysis-quality/TTCallGraphShared.ql
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @name Shared call graph edge from using type-tracking instead of points-to
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id py/meta/call-graph-shared
|
||||
* @tags meta
|
||||
* @precision very-low
|
||||
*/
|
||||
|
||||
import python
|
||||
import CallGraphQuality
|
||||
|
||||
from CallNode call, Target target
|
||||
where
|
||||
target.isRelevant() and
|
||||
call.(PointsToBasedCallGraph::ResolvableCall).getTarget() = target and
|
||||
call.(TypeTrackingBasedCallGraph::ResolvableCall).getTarget() = target
|
||||
select call, "SHARED: $@ to $@", call, "Call", target, target.toString()
|
||||
Reference in New Issue
Block a user