-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathStatementNoEffect.ql
More file actions
120 lines (107 loc) · 3.5 KB
/
StatementNoEffect.ql
File metadata and controls
120 lines (107 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* @name Statement has no effect
* @description A statement has no effect
* @kind problem
* @tags maintainability
* useless-code
* external/cwe/cwe-561
* @problem.severity recommendation
* @sub-severity high
* @precision high
* @id py/ineffectual-statement
*/
import python
predicate understood_attribute(Attribute attr, ClassObject cls, ClassObject attr_cls) {
exists(string name |
attr.getName() = name |
attr.getObject().refersTo(_, cls, _) and
cls.attributeRefersTo(name, _, attr_cls, _)
)
}
/* Conservative estimate of whether attribute lookup has a side effect */
predicate side_effecting_attribute(Attribute attr) {
exists(ClassObject cls, ClassObject attr_cls |
understood_attribute(attr, cls, attr_cls) and
side_effecting_descriptor_type(attr_cls)
)
}
predicate maybe_side_effecting_attribute(Attribute attr) {
not understood_attribute(attr, _, _) and not attr.refersTo(_)
or
side_effecting_attribute(attr)
}
predicate side_effecting_descriptor_type(ClassObject descriptor) {
descriptor.isDescriptorType() and
/* Technically all descriptor gets have side effects,
* but some are indicative of a missing call and
* we want to treat them as having no effect. */
not descriptor = thePyFunctionType() and
not descriptor = theStaticMethodType() and
not descriptor = theClassMethodType()
}
/** Side effecting binary operators are rare, so we assume they are not
* side-effecting unless we know otherwise.
*/
predicate side_effecting_binary(Expr b) {
exists(Expr sub, string method_name |
sub = b.(BinaryExpr).getLeft() and
method_name = b.(BinaryExpr).getOp().getSpecialMethodName()
or
exists(Cmpop op |
b.(Compare).compares(sub, op, _) and
method_name = op.getSpecialMethodName()
)
|
exists(ClassObject cls |
sub.refersTo(_, cls, _) and
cls.hasAttribute(method_name)
and
not exists(ClassObject declaring |
declaring.declaresAttribute(method_name)
and declaring = cls.getAnImproperSuperType() and
declaring.isBuiltin() and not declaring = theObjectType()
)
)
)
}
predicate is_notebook(File f) {
exists(Comment c |
c.getLocation().getFile() = f |
c.getText().regexpMatch("#\\s*<nbformat>.+</nbformat>\\s*")
)
}
/** Expression (statement) in a jupyter/ipython notebook */
predicate in_notebook(Expr e) {
is_notebook(e.getScope().(Module).getFile())
}
FunctionObject assertRaises() {
exists(ModuleObject unittest, ClassObject testcase |
unittest.getName() = "unittest" and
testcase = unittest.getAttribute("TestCase") and
result = testcase.lookupAttribute("assertRaises")
)
}
/** Holds if expression `e` is in a `with` block that tests for exceptions being raised. */
predicate in_raises_test(Expr e) {
exists(With w |
w.contains(e) and
w.getContextExpr() = assertRaises().getACall().getNode()
)
}
predicate no_effect(Expr e) {
not e instanceof StrConst and
not ((StrConst)e).isDocString() and
not e.hasSideEffects() and
forall(Expr sub |
sub = e.getASubExpression*()
|
not side_effecting_binary(sub)
and
not maybe_side_effecting_attribute(sub)
) and
not in_notebook(e) and
not in_raises_test(e)
}
from ExprStmt stmt
where no_effect(stmt.getValue())
select stmt, "This statement has no effect."