-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18599 from joefarebrother/python-qual-not-named-s…
…elf-cls Python: Modernize py/not-named-self and py/not-named-cls queries
- Loading branch information
Showing
14 changed files
with
189 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/** Definitions for reasoning about the expected first argument names for methods. */ | ||
|
||
import python | ||
import semmle.python.ApiGraphs | ||
import semmle.python.dataflow.new.internal.DataFlowDispatch | ||
import DataFlow | ||
|
||
/** Holds if `f` is a method of the class `c`. */ | ||
private predicate methodOfClass(Function f, Class c) { | ||
exists(FunctionDef d | d.getDefinedFunction() = f and d.getScope() = c) | ||
} | ||
|
||
/** Holds if `c` is a metaclass. */ | ||
private predicate isMetaclass(Class c) { | ||
c = API::builtin("type").getASubclass*().asSource().asExpr().(ClassExpr).getInnerScope() | ||
} | ||
|
||
/** Holds if `c` is a Zope interface. */ | ||
private predicate isZopeInterface(Class c) { | ||
c = | ||
API::moduleImport("zope") | ||
.getMember("interface") | ||
.getMember("Interface") | ||
.getASubclass*() | ||
.asSource() | ||
.asExpr() | ||
.(ClassExpr) | ||
.getInnerScope() | ||
} | ||
|
||
/** | ||
* Holds if `f` is used in the initialisation of `c`. | ||
* This means `f` isn't being used as a normal method. | ||
* Ideally it should be a `@staticmethod`; however this wasn't possible prior to Python 3.10. | ||
* We exclude this case from the `not-named-self` query. | ||
* However there is potential for a new query that specifically covers and alerts for this case. | ||
*/ | ||
private predicate usedInInit(Function f, Class c) { | ||
methodOfClass(f, c) and | ||
exists(Call call | | ||
call.getScope() = c and | ||
DataFlow::localFlow(DataFlow::exprNode(f.getDefinition()), DataFlow::exprNode(call.getFunc())) | ||
) | ||
} | ||
|
||
/** | ||
* Holds if `f` has no arguments, and also has a decorator. | ||
* We assume that the decorator affect the method in such a way that a `self` parameter is unneeded. | ||
*/ | ||
private predicate noArgsWithDecorator(Function f) { | ||
not exists(f.getAnArg()) and | ||
exists(f.getADecorator()) | ||
} | ||
|
||
/** Holds if the first parameter of `f` should be named `self`. */ | ||
predicate shouldBeSelf(Function f, Class c) { | ||
methodOfClass(f, c) and | ||
not isStaticmethod(f) and | ||
not isClassmethod(f) and | ||
not isMetaclass(c) and | ||
not isZopeInterface(c) and | ||
not usedInInit(f, c) and | ||
not noArgsWithDecorator(f) | ||
} | ||
|
||
/** Holds if the first parameter of `f` should be named `cls`. */ | ||
predicate shouldBeCls(Function f, Class c) { | ||
methodOfClass(f, c) and | ||
not isStaticmethod(f) and | ||
( | ||
isClassmethod(f) and not isMetaclass(c) | ||
or | ||
isMetaclass(c) and not isClassmethod(f) | ||
) | ||
} | ||
|
||
/** Holds if the first parameter of `f` is named `self`. */ | ||
predicate firstArgNamedSelf(Function f) { f.getArgName(0) = "self" } | ||
|
||
/** 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. */ | ||
predicate firstArgRefersToCls(Function f, Class c) { | ||
methodOfClass(f, c) and | ||
exists(string argname | argname = f.getArgName(0) | | ||
argname = "cls" | ||
or | ||
/* Not PEP8, but relatively common */ | ||
argname = "mcls" | ||
or | ||
/* If c is a metaclass, allow arguments named `self`. */ | ||
argname = "self" and | ||
isMetaclass(c) | ||
) | ||
} | ||
|
||
/** Holds if the first parameter of `f` should be named `self`, but isn't. */ | ||
predicate firstArgShouldBeNamedSelfAndIsnt(Function f) { | ||
shouldBeSelf(f, _) and | ||
not firstArgNamedSelf(f) | ||
} | ||
|
||
/** Holds if the first parameter of `f` should be named `cls`, but isn't. */ | ||
predicate firstArgShouldReferToClsAndDoesnt(Function f) { | ||
exists(Class c | | ||
methodOfClass(f, c) and | ||
shouldBeCls(f, c) and | ||
not firstArgRefersToCls(f, c) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
class Entry(object): | ||
@classmethod | ||
def make(klass): | ||
def make(self): | ||
return Entry() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
class Point: | ||
def __init__(val, x, y): # first parameter is mis-named 'val' | ||
def __init__(val, x, y): # BAD: first parameter is mis-named 'val' | ||
val._x = x | ||
val._y = y | ||
|
||
class Point2: | ||
def __init__(self, x, y): # first parameter is correctly named 'self' | ||
def __init__(self, x, y): # GOOD: first parameter is correctly named 'self' | ||
self._x = x | ||
self._y = y |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
4 changes: 0 additions & 4 deletions
4
python/ql/test/query-tests/Functions/general/NonSelf.expected
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.