Python: Port remaining metrics queries

Essentially does what the points-to analysis did, only now it uses the
new call graph instead.

No test changes.
This commit is contained in:
Taus
2026-02-27 16:48:12 +00:00
parent 3277671428
commit 1c659e6fbd
6 changed files with 82 additions and 15 deletions

View File

@@ -438,7 +438,7 @@ class FunctionMetricsWithPointsTo extends FunctionMetrics {
* One callable "this" depends on another callable "result"
* if "this" makes some call to a method that may end up being "result".
*/
FunctionMetricsWithPointsTo getADependency() {
override FunctionMetricsWithPointsTo getADependency() {
result != this and
not non_coupling_method(result) and
exists(Call call | call.getScope() = this |
@@ -462,7 +462,7 @@ class FunctionMetricsWithPointsTo extends FunctionMetrics {
* the number of callables that depend on this method.
* This is sometimes called the "fan-in" of a method.
*/
int getAfferentCoupling() {
override int getAfferentCoupling() {
result = count(FunctionMetricsWithPointsTo m | m.getADependency() = this)
}
@@ -471,7 +471,7 @@ class FunctionMetricsWithPointsTo extends FunctionMetrics {
* the number of methods that this method depends on
* This is sometimes called the "fan-out" of a method.
*/
int getEfferentCoupling() {
override int getEfferentCoupling() {
result = count(FunctionMetricsWithPointsTo m | this.getADependency() = m)
}
@@ -500,13 +500,17 @@ class ClassMetricsWithPointsTo extends ClassMetrics {
* Gets the afferent coupling of a class -- the number of classes that
* directly depend on it.
*/
int getAfferentCoupling() { result = count(ClassMetricsWithPointsTo t | t.dependsOn(this)) }
override int getAfferentCoupling() {
result = count(ClassMetricsWithPointsTo t | t.dependsOn(this))
}
/**
* Gets the efferent coupling of a class -- the number of classes that
* it directly depends on.
*/
int getEfferentCoupling() { result = count(ClassMetricsWithPointsTo t | this.dependsOn(t)) }
override int getEfferentCoupling() {
result = count(ClassMetricsWithPointsTo t | this.dependsOn(t))
}
/** Gets the depth of inheritance of the class. */
int getInheritanceDepth() {
@@ -537,13 +541,17 @@ class ModuleMetricsWithPointsTo extends ModuleMetrics {
* Gets the afferent coupling of a module -- the number of modules that
* directly depend on it.
*/
int getAfferentCoupling() { result = count(ModuleMetricsWithPointsTo t | t.dependsOn(this)) }
override int getAfferentCoupling() {
result = count(ModuleMetricsWithPointsTo t | t.dependsOn(this))
}
/**
* Gets the efferent coupling of a module -- the number of modules that
* it directly depends on.
*/
int getEfferentCoupling() { result = count(ModuleMetricsWithPointsTo t | this.dependsOn(t)) }
override int getEfferentCoupling() {
result = count(ModuleMetricsWithPointsTo t | this.dependsOn(t))
}
private predicate dependsOn(Module other) {
other != this and

View File

@@ -54,6 +54,31 @@ class FunctionMetrics extends Function {
result = e - n + 2
)
}
/**
* Gets a function that this function depends on via a call.
* One callable depends on another if it makes a call that resolves to that callable.
*/
FunctionMetrics getADependency() {
result != this and
not non_coupling_method(result) and
exists(CallNode call |
call.getScope() = this and
resolveCall(call, result, _)
)
}
/**
* Gets the afferent coupling of this function -- the number of
* functions that depend on it.
*/
int getAfferentCoupling() { result = count(FunctionMetrics m | m.getADependency() = this) }
/**
* Gets the efferent coupling of this function -- the number of
* functions that it depends on.
*/
int getEfferentCoupling() { result = count(FunctionMetrics m | this.getADependency() = m) }
}
/** The metrics for a class */
@@ -170,6 +195,25 @@ class ClassMetrics extends Class {
/** return Hitz and Montazeri Lack of Cohesion */
int getLackOfCohesionHM() { result = count(int line | this.unionSubgraph(_, line)) }
private predicate dependsOn(Class other) {
other != this and
exists(FunctionMetrics f1, FunctionMetrics f2 | f1.getADependency() = f2 |
f1.getScope() = this and f2.getScope() = other
)
}
/**
* Gets the afferent coupling of this class -- the number of classes that
* directly depend on it.
*/
int getAfferentCoupling() { result = count(ClassMetrics t | t.dependsOn(this)) }
/**
* Gets the efferent coupling of this class -- the number of classes that
* it directly depends on.
*/
int getEfferentCoupling() { result = count(ClassMetrics t | this.dependsOn(t)) }
}
class ModuleMetrics extends Module {
@@ -184,6 +228,25 @@ class ModuleMetrics extends Module {
/** Gets the number of lines of docstrings in the module */
int getNumberOfLinesOfDocStrings() { py_docstringlines(this, result) }
private predicate dependsOn(Module other) {
other != this and
exists(FunctionMetrics f1, FunctionMetrics f2 | f1.getADependency() = f2 |
f1.getEnclosingModule() = this and f2.getEnclosingModule() = other
)
}
/**
* Gets the afferent coupling of this module -- the number of modules that
* directly depend on it.
*/
int getAfferentCoupling() { result = count(ModuleMetrics t | t.dependsOn(this)) }
/**
* Gets the efferent coupling of this module -- the number of modules that
* it directly depends on.
*/
int getEfferentCoupling() { result = count(ModuleMetrics t | this.dependsOn(t)) }
}
predicate non_coupling_method(Function f) {

View File

@@ -11,7 +11,6 @@
*/
import python
private import LegacyPointsTo
from ClassMetricsWithPointsTo cls
from ClassMetrics cls
select cls, cls.getAfferentCoupling() as n order by n desc

View File

@@ -11,7 +11,6 @@
*/
import python
private import LegacyPointsTo
from ClassMetricsWithPointsTo cls
from ClassMetrics cls
select cls, cls.getEfferentCoupling() as n order by n desc

View File

@@ -11,7 +11,6 @@
*/
import python
private import LegacyPointsTo
from ModuleMetricsWithPointsTo m
from ModuleMetrics m
select m, m.getAfferentCoupling() as n order by n desc

View File

@@ -11,7 +11,6 @@
*/
import python
private import LegacyPointsTo
from ModuleMetricsWithPointsTo m
from ModuleMetrics m
select m, m.getEfferentCoupling() as n order by n desc