Skip to content

Commit 526e235

Browse files
Update NonSelf and NonCls queries
1 parent f114053 commit 526e235

File tree

3 files changed

+85
-49
lines changed

3 files changed

+85
-49
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/** Definitions for reasoning about the expected first argument names for methods. */
2+
3+
import python
4+
import semmle.python.ApiGraphs
5+
6+
/** Holds if `f` is a method of the class `c`. */
7+
private predicate methodOfClass(Function f, Class c) { f.getScope() = c }
8+
9+
/** Holds if `c` is a metaclass. */
10+
private predicate isMetaclass(Class c) {
11+
c.getABase() = API::builtin("type").getASubclass*().asSource().asExpr()
12+
}
13+
14+
/** Holds if `f` is a class method. */
15+
private predicate isClassMethod(Function f) {
16+
f.getADecorator() = API::builtin("classmethod").asSource().asExpr()
17+
}
18+
19+
/** Holds if `f` is a static method. */
20+
private predicate isStaticMethod(Function f) {
21+
f.getADecorator() = API::builtin("staticmethod").asSource().asExpr()
22+
}
23+
24+
/** Holds if `c` is a Zope interface. */
25+
private predicate isZopeInterface(Class c) {
26+
c.getABase() =
27+
API::moduleImport("zone")
28+
.getMember("interface")
29+
.getMember("interface")
30+
.getASubclass*()
31+
.asSource()
32+
.asExpr()
33+
}
34+
35+
/** Holds if the first parameter of `f` should be named `self`. */
36+
predicate shouldBeSelf(Function f, Class c) {
37+
methodOfClass(f, c) and
38+
not isStaticMethod(f) and
39+
not isClassMethod(f) and
40+
not f.getName() in ["__new__", "__init_subclass__", "__metaclass__", "__class_getitem__"] and
41+
isMetaclass(c) and
42+
not isZopeInterface(c)
43+
}
44+
45+
/** Holds if the first parameter of `f` should be named `cls`. */
46+
predicate shouldBeCls(Function f, Class c) {
47+
methodOfClass(f, c) and
48+
not isStaticMethod(f) and
49+
(
50+
isClassMethod(f)
51+
or
52+
f.getName() in ["__new__", "__init_subclass__", "__metaclass__", "__class_getitem__"]
53+
)
54+
}
55+
56+
/** Holds if the first parameter of `f` is named `self`. */
57+
predicate firstArgNamedSelf(Function f) { f.getArgName(0) = "self" }
58+
59+
/** Holds if the first parameter of `f` is named `cls`. */
60+
predicate firstArgNamedCls(Function f) {
61+
exists(string argname | argname = f.getArgName(0) |
62+
argname = "cls"
63+
or
64+
/* Not PEP8, but relatively common */
65+
argname = "mcls"
66+
)
67+
}
68+
69+
/** Holds if the first parameter of `f` should be named `self`, but isn't. */
70+
predicate firstArgShouldBeNamedSelfAndIsnt(Function f) {
71+
exists(Class c | shouldBeSelf(f, c)) and
72+
not firstArgNamedSelf(f)
73+
}
74+
75+
/** Holds if the first parameter of `f` should be named `cls`, but isn't. */
76+
predicate firstArgShouldBeNamedClsAndIsnt(Function f) {
77+
exists(Class c | shouldBeCls(f, c)) and
78+
not firstArgNamedCls(f)
79+
}

python/ql/src/Functions/NonCls.ql

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

1515
import python
16-
17-
predicate first_arg_cls(Function f) {
18-
exists(string argname | argname = f.getArgName(0) |
19-
argname = "cls"
20-
or
21-
/* Not PEP8, but relatively common */
22-
argname = "mcls"
23-
)
24-
}
25-
26-
predicate is_type_method(Function f) {
27-
exists(ClassValue c | c.getScope() = f.getScope() and c.getASuperType() = ClassValue::type())
28-
}
29-
30-
predicate classmethod_decorators_only(Function f) {
31-
forall(Expr decorator | decorator = f.getADecorator() | decorator.(Name).getId() = "classmethod")
32-
}
16+
import MethodArgNames
3317

3418
from Function f, string message
3519
where
36-
(f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and
37-
not first_arg_cls(f) and
38-
classmethod_decorators_only(f) and
39-
not f.getName() = "__new__" and
20+
firstArgShouldBeNamedClsAndIsnt(f) and
4021
(
4122
if exists(f.getArgName(0))
4223
then

python/ql/src/Functions/NonSelf.ql

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,11 @@
1414
*/
1515

1616
import python
17-
import semmle.python.libraries.Zope
17+
import MethodArgNames
1818

19-
predicate is_type_method(FunctionValue fv) {
20-
exists(ClassValue c | c.declaredAttribute(_) = fv and c.getASuperType() = ClassValue::type())
21-
}
22-
23-
predicate used_in_defining_scope(FunctionValue fv) {
24-
exists(Call c | c.getScope() = fv.getScope().getScope() and c.getFunc().pointsTo(fv))
25-
}
26-
27-
from Function f, FunctionValue fv, string message
19+
from Function f, string message
2820
where
29-
exists(ClassValue cls, string name |
30-
cls.declaredAttribute(name) = fv and
31-
cls.isNewStyle() and
32-
not name = "__new__" and
33-
not name = "__metaclass__" and
34-
not name = "__init_subclass__" and
35-
not name = "__class_getitem__" and
36-
/* declared in scope */
37-
f.getScope() = cls.getScope()
38-
) and
39-
not f.getArgName(0) = "self" and
40-
not is_type_method(fv) and
41-
fv.getScope() = f and
42-
not f.getName() = "lambda" and
43-
not used_in_defining_scope(fv) and
21+
firstArgShouldBeNamedSelfAndIsnt(f) and
4422
(
4523
(
4624
if exists(f.getArgName(0))
@@ -52,7 +30,5 @@ where
5230
message =
5331
"Normal methods should have at least one parameter (the first of which should be 'self')."
5432
) and
55-
not f.hasVarArg()
56-
) and
57-
not fv instanceof ZopeInterfaceMethodValue
33+
)
5834
select f, message

0 commit comments

Comments
 (0)