Skip to content

Commit 61d5a69

Browse files
Refactor metaclass logic a bit, ensure lambdas are excluded
1 parent 287cf01 commit 61d5a69

File tree

4 files changed

+31
-34
lines changed

4 files changed

+31
-34
lines changed

python/ql/src/Functions/MethodArgNames.qll

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,19 @@
22

33
import python
44
import semmle.python.ApiGraphs
5+
import semmle.python.dataflow.new.internal.DataFlowDispatch
56
import DataFlow
67

78
/** Holds if `f` is a method of the class `c`. */
8-
private predicate methodOfClass(Function f, Class c) { f.getScope() = c }
9+
private predicate methodOfClass(Function f, Class c) {
10+
exists(FunctionDef d | d.getDefinedFunction() = f and d.getScope() = c)
11+
}
912

1013
/** Holds if `c` is a metaclass. */
1114
private predicate isMetaclass(Class c) {
1215
c = API::builtin("type").getASubclass*().asSource().asExpr().(ClassExpr).getInnerScope()
1316
}
1417

15-
/** Holds if `f` is a class method. */
16-
private predicate isClassMethod(Function f) {
17-
f.getADecorator() = API::builtin("classmethod").asSource().asExpr()
18-
or
19-
f.getName() in ["__new__", "__init_subclass__", "__metaclass__", "__class_getitem__"]
20-
}
21-
22-
/** Holds if `f` is a static method. */
23-
private predicate isStaticMethod(Function f) {
24-
f.getADecorator() = API::builtin("staticmethod").asSource().asExpr()
25-
}
26-
2718
/** Holds if `c` is a Zope interface. */
2819
private predicate isZopeInterface(Class c) {
2920
c =
@@ -55,8 +46,8 @@ private predicate usedInInit(Function f, Class c) {
5546
/** Holds if the first parameter of `f` should be named `self`. */
5647
predicate shouldBeSelf(Function f, Class c) {
5748
methodOfClass(f, c) and
58-
not isStaticMethod(f) and
59-
not isClassMethod(f) and
49+
not isStaticmethod(f) and
50+
not isClassmethod(f) and
6051
not isMetaclass(c) and
6152
not isZopeInterface(c) and
6253
not usedInInit(f, c)
@@ -65,24 +56,29 @@ predicate shouldBeSelf(Function f, Class c) {
6556
/** Holds if the first parameter of `f` should be named `cls`. */
6657
predicate shouldBeCls(Function f, Class c) {
6758
methodOfClass(f, c) and
68-
not isStaticMethod(f) and
59+
not isStaticmethod(f) and
6960
(
70-
isClassMethod(f) and not isMetaclass(c)
61+
isClassmethod(f) and not isMetaclass(c)
7162
or
72-
isMetaclass(c) and not isClassMethod(f)
63+
isMetaclass(c) and not isClassmethod(f)
7364
)
7465
}
7566

7667
/** Holds if the first parameter of `f` is named `self`. */
7768
predicate firstArgNamedSelf(Function f) { f.getArgName(0) = "self" }
7869

79-
/** Holds if the first parameter of `f` is named `cls`. */
80-
predicate firstArgNamedCls(Function f) {
70+
/** Holds if the first parameter of `f` refers to the class - it is either named `cls`, or it is named `self` and is a method of a metaclass. */
71+
predicate firstArgRefersToCls(Function f, Class c) {
72+
methodOfClass(f, c) and
8173
exists(string argname | argname = f.getArgName(0) |
8274
argname = "cls"
8375
or
8476
/* Not PEP8, but relatively common */
8577
argname = "mcls"
78+
or
79+
/* If c is a metaclass, allow arguments named `self`. */
80+
argname = "self" and
81+
isMetaclass(c)
8682
)
8783
}
8884

@@ -92,17 +88,11 @@ predicate firstArgShouldBeNamedSelfAndIsnt(Function f) {
9288
not firstArgNamedSelf(f)
9389
}
9490

95-
/** Holds if `f` is a regular method of a metaclass, and its first argument is named `self`. */
96-
private predicate metaclassNamedSelf(Function f, Class c) {
97-
methodOfClass(f, c) and
98-
firstArgNamedSelf(f) and
99-
isMetaclass(c) and
100-
not isClassMethod(f)
101-
}
102-
10391
/** Holds if the first parameter of `f` should be named `cls`, but isn't. */
104-
predicate firstArgShouldBeNamedClsAndIsnt(Function f) {
105-
shouldBeCls(f, _) and
106-
not firstArgNamedCls(f) and
107-
not metaclassNamedSelf(f, _)
92+
predicate firstArgShouldReferToClsAndDoesnt(Function f) {
93+
exists(Class c |
94+
methodOfClass(f, c) and
95+
shouldBeCls(f, c) and
96+
not firstArgRefersToCls(f, c)
97+
)
10898
}

python/ql/src/Functions/NonCls.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import MethodArgNames
1616

1717
from Function f, string message
1818
where
19-
firstArgShouldBeNamedClsAndIsnt(f) and
19+
firstArgShouldReferToClsAndDoesnt(f) and
2020
(
2121
if exists(f.getArgName(0))
2222
then

python/ql/test/query-tests/Functions/methodArgNames/parameter_names.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,10 @@ def __init_subclass__(cls):
141141

142142
def __class_getitem__(cls):
143143
pass
144+
145+
from dataclasses import dataclass, field
146+
147+
@dataclass
148+
class A:
149+
# Lambdas used in initilisation aren't methods.
150+
x: int = field(default_factory = lambda: 2)

python/ql/test/query-tests/Functions/methodArgNames/test.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module MethodArgTest implements TestSig {
1414
firstArgShouldBeNamedSelfAndIsnt(f) and
1515
tag = "shouldBeSelf"
1616
or
17-
firstArgShouldBeNamedClsAndIsnt(f) and
17+
firstArgShouldReferToClsAndDoesnt(f) and
1818
tag = "shouldBeCls"
1919
)
2020
)

0 commit comments

Comments
 (0)