-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathSignatureSpecialMethods.ql
More file actions
174 lines (161 loc) · 5.89 KB
/
SignatureSpecialMethods.ql
File metadata and controls
174 lines (161 loc) · 5.89 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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**
* @name Special method has incorrect signature
* @description Special method has incorrect signature
* @kind problem
* @tags reliability
* correctness
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/special-method-wrong-signature
*/
import python
predicate is_unary_op(string name) {
name in [
"__del__", "__repr__", "__neg__", "__pos__", "__abs__", "__invert__", "__complex__",
"__int__", "__float__", "__long__", "__oct__", "__hex__", "__str__", "__index__", "__enter__",
"__hash__", "__bool__", "__nonzero__", "__unicode__", "__len__", "__iter__", "__reversed__"
]
}
predicate is_binary_op(string name) {
name in [
"__lt__", "__le__", "__delattr__", "__delete__", "__instancecheck__", "__subclasscheck__",
"__getitem__", "__delitem__", "__contains__", "__add__", "__sub__", "__mul__", "__eq__",
"__floordiv__", "__div__", "__truediv__", "__mod__", "__divmod__", "__lshift__", "__rshift__",
"__and__", "__xor__", "__or__", "__ne__", "__radd__", "__rsub__", "__rmul__", "__rfloordiv__",
"__rdiv__", "__rtruediv__", "__rmod__", "__rdivmod__", "__rpow__", "__rlshift__", "__gt__",
"__rrshift__", "__rand__", "__rxor__", "__ror__", "__iadd__", "__isub__", "__imul__",
"__ifloordiv__", "__idiv__", "__itruediv__", "__ge__", "__imod__", "__idivmod__", "__ipow__",
"__ilshift__", "__irshift__", "__iand__", "__ixor__", "__ior__", "__coerce__", "__cmp__",
"__rcmp__", "__getattr___", "__getattribute___"
]
}
predicate is_ternary_op(string name) {
name in ["__setattr__", "__set__", "__setitem__", "__getslice__", "__delslice__"]
}
predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" }
int argument_count(string name) {
is_unary_op(name) and result = 1
or
is_binary_op(name) and result = 2
or
is_ternary_op(name) and result = 3
or
is_quad_op(name) and result = 4
}
predicate incorrect_special_method_defn(
Function func, string message, boolean show_counts, string name, boolean is_unused_default
) {
exists(int required | required = argument_count(name) |
/* actual_non_default <= actual */
if required > func.getMaxPositionalArguments()
then message = "Too few parameters" and show_counts = true and is_unused_default = false
else
if required < func.getMinPositionalArguments()
then message = "Too many parameters" and show_counts = true and is_unused_default = false
else (
func.getMinPositionalArguments() < required and
not func.hasVarArg() and
message =
(required - func.getMinPositionalArguments()) + " default values(s) will never be used" and
show_counts = false and
is_unused_default = true
)
)
}
predicate incorrect_pow(
Function func, string message, boolean show_counts, boolean is_unused_default
) {
(
func.getMaxPositionalArguments() < 2 and
message = "Too few parameters" and
show_counts = true and
is_unused_default = false
or
func.getMinPositionalArguments() > 3 and
message = "Too many parameters" and
show_counts = true and
is_unused_default = false
or
func.getMinPositionalArguments() < 2 and
message = (2 - func.getMinPositionalArguments()) + " default value(s) will never be used" and
show_counts = false and
is_unused_default = true
or
func.getMinPositionalArguments() = 3 and
message = "Third parameter to __pow__ should have a default value" and
show_counts = false and
is_unused_default = false
)
}
predicate incorrect_get(
Function func, string message, boolean show_counts, boolean is_unused_default
) {
(
func.getMaxPositionalArguments() < 3 and
message = "Too few parameters" and
show_counts = true and
is_unused_default = false
or
func.getMinPositionalArguments() > 3 and
message = "Too many parameters" and
show_counts = true and
is_unused_default = false
or
func.getMinPositionalArguments() < 2 and
not func.hasVarArg() and
message = (2 - func.getMinPositionalArguments()) + " default value(s) will never be used" and
show_counts = false and
is_unused_default = true
)
}
string should_have_parameters(string name) {
if name in ["__pow__", "__get__"]
then result = "2 or 3"
else result = argument_count(name).toString()
}
string has_parameters(Function f) {
exists(int i | i = f.getMinPositionalArguments() |
i = 0 and result = "no parameters"
or
i = 1 and result = "1 parameter"
or
i > 1 and result = i.toString() + " parameters"
)
}
/** Holds if `f` is likely to be a placeholder, and hence not interesting enough to report. */
predicate isLikelyPlaceholderFunction(Function f) {
// Body has only a single statement.
f.getBody().getItem(0) = f.getBody().getLastItem() and
(
// Body is a string literal. This is a common pattern for Zope interfaces.
f.getBody().getLastItem().(ExprStmt).getValue() instanceof StringLiteral
or
// Body just raises an exception.
f.getBody().getLastItem() instanceof Raise
)
}
from
PythonFunctionValue f, string message, string sizes, boolean show_counts, string name,
ClassValue owner, boolean show_unused_defaults
where
owner.declaredAttribute(name) = f and
(
incorrect_special_method_defn(f.getScope(), message, show_counts, name, show_unused_defaults)
or
incorrect_pow(f.getScope(), message, show_counts, show_unused_defaults) and name = "__pow__"
or
incorrect_get(f.getScope(), message, show_counts, show_unused_defaults) and name = "__get__"
or
) and
not isLikelyPlaceholderFunction(f.getScope()) and
show_unused_defaults = false and
(
show_counts = false and sizes = ""
or
show_counts = true and
sizes =
", which has " + has_parameters(f.getScope()) + ", but should have " +
should_have_parameters(name)
)
select f, message + " for special method " + name + sizes + ", in class $@.", owner, owner.getName()