Skip to content

Commit 38ab4fd

Browse files
committed
Python: Remove points-to from Metrics.qll
Moves the classes/predicates that _actually_ depend on points-to to the `LegacyPointsTo` module, leaving behind a module that contains all of the metrics-related stuff (line counts, nesting depth, etc.) that don't need points-to to be evaluated. Consequently, `Metrics` is now no longer a private import in `python.qll`.
1 parent 6b6d886 commit 38ab4fd

File tree

3 files changed

+181
-152
lines changed

3 files changed

+181
-152
lines changed

python/ql/lib/LegacyPointsTo.qll

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,178 @@ private predicate exits_early(BasicBlock b) {
430430
f.getACall().getBasicBlock() = b
431431
)
432432
}
433+
434+
/** The metrics for a function that require points-to analysis */
435+
class FunctionMetricsWithPointsTo extends FunctionMetrics {
436+
/**
437+
* Gets the cyclomatic complexity of the function:
438+
* The number of linearly independent paths through the source code.
439+
* Computed as E - N + 2P,
440+
* where
441+
* E = the number of edges of the graph.
442+
* N = the number of nodes of the graph.
443+
* P = the number of connected components, which for a single function is 1.
444+
*/
445+
int getCyclomaticComplexity() {
446+
exists(int e, int n |
447+
n = count(BasicBlockWithPointsTo b | b = this.getABasicBlock() and b.likelyReachable()) and
448+
e =
449+
count(BasicBlockWithPointsTo b1, BasicBlockWithPointsTo b2 |
450+
b1 = this.getABasicBlock() and
451+
b1.likelyReachable() and
452+
b2 = this.getABasicBlock() and
453+
b2.likelyReachable() and
454+
b2 = b1.getASuccessor() and
455+
not b1.unlikelySuccessor(b2)
456+
)
457+
|
458+
result = e - n + 2
459+
)
460+
}
461+
462+
private BasicBlock getABasicBlock() {
463+
result = this.getEntryNode().getBasicBlock()
464+
or
465+
exists(BasicBlock mid | mid = this.getABasicBlock() and result = mid.getASuccessor())
466+
}
467+
468+
/**
469+
* Dependency of Callables
470+
* One callable "this" depends on another callable "result"
471+
* if "this" makes some call to a method that may end up being "result".
472+
*/
473+
FunctionMetricsWithPointsTo getADependency() {
474+
result != this and
475+
not non_coupling_method(result) and
476+
exists(Call call | call.getScope() = this |
477+
exists(FunctionObject callee | callee.getFunction() = result |
478+
call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
479+
)
480+
or
481+
exists(Attribute a | call.getFunc() = a |
482+
unique_root_method(result, a.getName())
483+
or
484+
exists(Name n | a.getObject() = n and n.getId() = "self" |
485+
result.getScope() = this.getScope() and
486+
result.getName() = a.getName()
487+
)
488+
)
489+
)
490+
}
491+
492+
/**
493+
* Afferent Coupling
494+
* the number of callables that depend on this method.
495+
* This is sometimes called the "fan-in" of a method.
496+
*/
497+
int getAfferentCoupling() {
498+
result = count(FunctionMetricsWithPointsTo m | m.getADependency() = this)
499+
}
500+
501+
/**
502+
* Efferent Coupling
503+
* the number of methods that this method depends on
504+
* This is sometimes called the "fan-out" of a method.
505+
*/
506+
int getEfferentCoupling() {
507+
result = count(FunctionMetricsWithPointsTo m | this.getADependency() = m)
508+
}
509+
510+
override string getAQlClass() { none() }
511+
}
512+
513+
/** The metrics for a class that require points-to analysis */
514+
class ClassMetricsWithPointsTo extends ClassMetrics {
515+
private predicate dependsOn(Class other) {
516+
other != this and
517+
(
518+
exists(FunctionMetricsWithPointsTo f1, FunctionMetricsWithPointsTo f2 |
519+
f1.getADependency() = f2
520+
|
521+
f1.getScope() = this and f2.getScope() = other
522+
)
523+
or
524+
exists(Function f, Call c, ClassObject cls | c.getScope() = f and f.getScope() = this |
525+
c.getFunc().(ExprWithPointsTo).refersTo(cls) and
526+
cls.getPyClass() = other
527+
)
528+
)
529+
}
530+
531+
/**
532+
* Gets the afferent coupling of a class -- the number of classes that
533+
* directly depend on it.
534+
*/
535+
int getAfferentCoupling() { result = count(ClassMetricsWithPointsTo t | t.dependsOn(this)) }
536+
537+
/**
538+
* Gets the efferent coupling of a class -- the number of classes that
539+
* it directly depends on.
540+
*/
541+
int getEfferentCoupling() { result = count(ClassMetricsWithPointsTo t | this.dependsOn(t)) }
542+
543+
int getInheritanceDepth() {
544+
exists(ClassObject cls | cls.getPyClass() = this | result = max(classInheritanceDepth(cls)))
545+
}
546+
547+
override string getAQlClass() { none() }
548+
}
549+
550+
private int classInheritanceDepth(ClassObject cls) {
551+
/* Prevent run-away recursion in case of circular inheritance */
552+
not cls.getASuperType() = cls and
553+
(
554+
exists(ClassObject sup | cls.getABaseType() = sup | result = classInheritanceDepth(sup) + 1)
555+
or
556+
not exists(cls.getABaseType()) and
557+
(
558+
major_version() = 2 and result = 0
559+
or
560+
major_version() > 2 and result = 1
561+
)
562+
)
563+
}
564+
565+
/** The metrics for a module that require points-to analysis */
566+
class ModuleMetricsWithPointsTo extends ModuleMetrics {
567+
/**
568+
* Gets the afferent coupling of a module -- the number of modules that
569+
* directly depend on it.
570+
*/
571+
int getAfferentCoupling() { result = count(ModuleMetricsWithPointsTo t | t.dependsOn(this)) }
572+
573+
/**
574+
* Gets the efferent coupling of a module -- the number of modules that
575+
* it directly depends on.
576+
*/
577+
int getEfferentCoupling() { result = count(ModuleMetricsWithPointsTo t | this.dependsOn(t)) }
578+
579+
private predicate dependsOn(Module other) {
580+
other != this and
581+
(
582+
exists(FunctionMetricsWithPointsTo f1, FunctionMetricsWithPointsTo f2 |
583+
f1.getADependency() = f2
584+
|
585+
f1.getEnclosingModule() = this and f2.getEnclosingModule() = other
586+
)
587+
or
588+
exists(Function f, Call c, ClassObject cls | c.getScope() = f and f.getScope() = this |
589+
c.getFunc().(ExprWithPointsTo).refersTo(cls) and
590+
cls.getPyClass().getEnclosingModule() = other
591+
)
592+
)
593+
}
594+
595+
override string getAQlClass() { none() }
596+
}
597+
598+
/** Helpers for coupling */
599+
predicate unique_root_method(Function func, string name) {
600+
name = func.getName() and
601+
not exists(FunctionObject f, FunctionObject other |
602+
f.getFunction() = func and
603+
other.getName() = name
604+
|
605+
not other.overrides(f)
606+
)
607+
}

python/ql/lib/python.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import semmle.python.Patterns
1414
import semmle.python.Keywords
1515
import semmle.python.Comprehensions
1616
import semmle.python.Flow
17-
private import semmle.python.Metrics
17+
import semmle.python.Metrics
1818
import semmle.python.Constants
1919
import semmle.python.Scope
2020
import semmle.python.Comment

python/ql/lib/semmle/python/Metrics.qll

Lines changed: 5 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import python
2-
private import LegacyPointsTo
2+
private import semmle.python.SelfAttribute
33

44
/** The metrics for a function */
55
class FunctionMetrics extends Function {
@@ -18,76 +18,6 @@ class FunctionMetrics extends Function {
1818
/** Gets the number of lines of docstring in the function */
1919
int getNumberOfLinesOfDocStrings() { py_docstringlines(this, result) }
2020

21-
/**
22-
* Gets the cyclomatic complexity of the function:
23-
* The number of linearly independent paths through the source code.
24-
* Computed as E - N + 2P,
25-
* where
26-
* E = the number of edges of the graph.
27-
* N = the number of nodes of the graph.
28-
* P = the number of connected components, which for a single function is 1.
29-
*/
30-
int getCyclomaticComplexity() {
31-
exists(int e, int n |
32-
n = count(BasicBlockWithPointsTo b | b = this.getABasicBlock() and b.likelyReachable()) and
33-
e =
34-
count(BasicBlockWithPointsTo b1, BasicBlockWithPointsTo b2 |
35-
b1 = this.getABasicBlock() and
36-
b1.likelyReachable() and
37-
b2 = this.getABasicBlock() and
38-
b2.likelyReachable() and
39-
b2 = b1.getASuccessor() and
40-
not b1.unlikelySuccessor(b2)
41-
)
42-
|
43-
result = e - n + 2
44-
)
45-
}
46-
47-
private BasicBlock getABasicBlock() {
48-
result = this.getEntryNode().getBasicBlock()
49-
or
50-
exists(BasicBlock mid | mid = this.getABasicBlock() and result = mid.getASuccessor())
51-
}
52-
53-
/**
54-
* Dependency of Callables
55-
* One callable "this" depends on another callable "result"
56-
* if "this" makes some call to a method that may end up being "result".
57-
*/
58-
FunctionMetrics getADependency() {
59-
result != this and
60-
not non_coupling_method(result) and
61-
exists(Call call | call.getScope() = this |
62-
exists(FunctionObject callee | callee.getFunction() = result |
63-
call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
64-
)
65-
or
66-
exists(Attribute a | call.getFunc() = a |
67-
unique_root_method(result, a.getName())
68-
or
69-
exists(Name n | a.getObject() = n and n.getId() = "self" |
70-
result.getScope() = this.getScope() and
71-
result.getName() = a.getName()
72-
)
73-
)
74-
)
75-
}
76-
77-
/**
78-
* Afferent Coupling
79-
* the number of callables that depend on this method.
80-
* This is sometimes called the "fan-in" of a method.
81-
*/
82-
int getAfferentCoupling() { result = count(FunctionMetrics m | m.getADependency() = this) }
83-
84-
/**
85-
* Efferent Coupling
86-
* the number of methods that this method depends on
87-
* This is sometimes called the "fan-out" of a method.
88-
*/
89-
int getEfferentCoupling() { result = count(FunctionMetrics m | this.getADependency() = m) }
90-
9121
int getNumberOfParametersWithoutDefault() {
9222
result =
9323
this.getPositionalParameterCount() -
@@ -97,6 +27,8 @@ class FunctionMetrics extends Function {
9727
int getStatementNestingDepth() { result = max(Stmt s | s.getScope() = this | getNestingDepth(s)) }
9828

9929
int getNumberOfCalls() { result = count(Call c | c.getScope() = this) }
30+
31+
override string getAQlClass() { result = "FunctionMetrics" }
10032
}
10133

10234
/** The metrics for a class */
@@ -116,36 +48,6 @@ class ClassMetrics extends Class {
11648
/** Gets the number of lines of docstrings in the class */
11749
int getNumberOfLinesOfDocStrings() { py_docstringlines(this, result) }
11850

119-
private predicate dependsOn(Class other) {
120-
other != this and
121-
(
122-
exists(FunctionMetrics f1, FunctionMetrics f2 | f1.getADependency() = f2 |
123-
f1.getScope() = this and f2.getScope() = other
124-
)
125-
or
126-
exists(Function f, Call c, ClassObject cls | c.getScope() = f and f.getScope() = this |
127-
c.getFunc().(ExprWithPointsTo).refersTo(cls) and
128-
cls.getPyClass() = other
129-
)
130-
)
131-
}
132-
133-
/**
134-
* Gets the afferent coupling of a class -- the number of classes that
135-
* directly depend on it.
136-
*/
137-
int getAfferentCoupling() { result = count(ClassMetrics t | t.dependsOn(this)) }
138-
139-
/**
140-
* Gets the efferent coupling of a class -- the number of classes that
141-
* it directly depends on.
142-
*/
143-
int getEfferentCoupling() { result = count(ClassMetrics t | this.dependsOn(t)) }
144-
145-
int getInheritanceDepth() {
146-
exists(ClassObject cls | cls.getPyClass() = this | result = max(classInheritanceDepth(cls)))
147-
}
148-
14951
/* -------- CHIDAMBER AND KEMERER LACK OF COHESION IN METHODS ------------ */
15052
/*
15153
* The aim of this metric is to try and determine whether a class
@@ -243,21 +145,8 @@ class ClassMetrics extends Class {
243145

244146
/** return Hitz and Montazeri Lack of Cohesion */
245147
int getLackOfCohesionHM() { result = count(int line | this.unionSubgraph(_, line)) }
246-
}
247148

248-
private int classInheritanceDepth(ClassObject cls) {
249-
/* Prevent run-away recursion in case of circular inheritance */
250-
not cls.getASuperType() = cls and
251-
(
252-
exists(ClassObject sup | cls.getABaseType() = sup | result = classInheritanceDepth(sup) + 1)
253-
or
254-
not exists(cls.getABaseType()) and
255-
(
256-
major_version() = 2 and result = 0
257-
or
258-
major_version() > 2 and result = 1
259-
)
260-
)
149+
override string getAQlClass() { result = "ClassMetrics" }
261150
}
262151

263152
class ModuleMetrics extends Module {
@@ -273,42 +162,7 @@ class ModuleMetrics extends Module {
273162
/** Gets the number of lines of docstrings in the module */
274163
int getNumberOfLinesOfDocStrings() { py_docstringlines(this, result) }
275164

276-
/**
277-
* Gets the afferent coupling of a class -- the number of classes that
278-
* directly depend on it.
279-
*/
280-
int getAfferentCoupling() { result = count(ModuleMetrics t | t.dependsOn(this)) }
281-
282-
/**
283-
* Gets the efferent coupling of a class -- the number of classes that
284-
* it directly depends on.
285-
*/
286-
int getEfferentCoupling() { result = count(ModuleMetrics t | this.dependsOn(t)) }
287-
288-
private predicate dependsOn(Module other) {
289-
other != this and
290-
(
291-
exists(FunctionMetrics f1, FunctionMetrics f2 | f1.getADependency() = f2 |
292-
f1.getEnclosingModule() = this and f2.getEnclosingModule() = other
293-
)
294-
or
295-
exists(Function f, Call c, ClassObject cls | c.getScope() = f and f.getScope() = this |
296-
c.getFunc().(ExprWithPointsTo).refersTo(cls) and
297-
cls.getPyClass().getEnclosingModule() = other
298-
)
299-
)
300-
}
301-
}
302-
303-
/** Helpers for coupling */
304-
predicate unique_root_method(Function func, string name) {
305-
name = func.getName() and
306-
not exists(FunctionObject f, FunctionObject other |
307-
f.getFunction() = func and
308-
other.getName() = name
309-
|
310-
not other.overrides(f)
311-
)
165+
override string getAQlClass() { result = "ModuleMetrics" }
312166
}
313167

314168
predicate non_coupling_method(Function f) {

0 commit comments

Comments
 (0)