Skip to content

Commit 6ddbb3d

Browse files
committed
Python: Port getCyclomaticComplexity function
Note that this does not give the exact same results as the old function. The main reason for this is missing modelling of what built-in functions/methods may raise exceptions (so, for instance we don't realise that `read()` may raise an `IOError`, which causes the cyclomatic complexity to change).
1 parent f6962a2 commit 6ddbb3d

4 files changed

Lines changed: 30 additions & 35 deletions

File tree

python/ql/lib/LegacyPointsTo.qll

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -433,38 +433,6 @@ private predicate exits_early(BasicBlock b) {
433433

434434
/** The metrics for a function that require points-to analysis */
435435
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-
468436
/**
469437
* Dependency of Callables
470438
* One callable "this" depends on another callable "result"

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import python
22
private import semmle.python.SelfAttribute
3+
private import semmle.python.dataflow.new.internal.DataFlowDispatch
34

45
/** The metrics for a function */
56
class FunctionMetrics extends Function {
@@ -27,6 +28,32 @@ class FunctionMetrics extends Function {
2728
int getStatementNestingDepth() { result = max(Stmt s | s.getScope() = this | getNestingDepth(s)) }
2829

2930
int getNumberOfCalls() { result = count(Call c | c.getScope() = this) }
31+
32+
/**
33+
* Gets the cyclomatic complexity of the function:
34+
* The number of linearly independent paths through the source code.
35+
* Computed as E - N + 2P,
36+
* where
37+
* E = the number of edges of the graph.
38+
* N = the number of nodes of the graph.
39+
* P = the number of connected components, which for a single function is 1.
40+
*/
41+
int getCyclomaticComplexity() {
42+
exists(int n, int e |
43+
n = count(BasicBlock b | b.getScope() = this and Reachability::likelyReachable(b)) and
44+
e =
45+
count(BasicBlock b1, BasicBlock b2 |
46+
b1.getScope() = this and
47+
Reachability::likelyReachable(b1) and
48+
b2.getScope() = this and
49+
Reachability::likelyReachable(b2) and
50+
b2 = b1.getASuccessor() and
51+
not Reachability::unlikelySuccessor(b1.getLastNode(), b2.firstNode())
52+
)
53+
|
54+
result = e - n + 2
55+
)
56+
}
3057
}
3158

3259
/** The metrics for a class */

python/ql/src/Metrics/CyclomaticComplexity.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
*/
1414

1515
import python
16-
private import LegacyPointsTo
16+
import semmle.python.Metrics
1717

18-
from FunctionMetricsWithPointsTo func, int complexity
18+
from FunctionMetrics func, int complexity
1919
where complexity = func.getCyclomaticComplexity()
2020
select func, complexity order by complexity desc
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
| code.py:35:1:35:21 | Function exceptions | 6 |
12
| code.py:23:1:23:17 | Function nested | 4 |
23
| code.py:12:1:12:21 | Function two_branch | 3 |
34
| code.py:6:1:6:18 | Function one_branch | 2 |
4-
| code.py:35:1:35:21 | Function exceptions | 2 |
55
| code.py:1:1:1:16 | Function f_linear | 1 |
66
| code.py:45:1:45:39 | Function must_be_positive | 1 |

0 commit comments

Comments
 (0)