Python: Consider __new__ a classmethod

This commit is contained in:
Rasmus Wriedt Larsen
2022-10-12 14:29:54 +02:00
parent f040ad8dac
commit 6fefd54533
3 changed files with 31 additions and 18 deletions

View File

@@ -174,18 +174,31 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
// =============================================================================
// Helper predicates
// =============================================================================
/** Holds if the function has a `staticmethod` decorator. */
predicate hasStaticmethodDecorator(Function func) {
/**
* Holds if the function `func` is a staticmethod -- either by having a
* `@staticmethod` decorator or by convention
* (like a `__new__` method on a class is a classmethod even without the decorator).
*/
predicate isStaticmethod(Function func) {
exists(NameNode id | id.getId() = "staticmethod" and id.isGlobal() |
func.getADecorator() = id.getNode()
)
}
/** Holds if the function has a `classmethod` decorator. */
predicate hasClassmethodDecorator(Function func) {
/**
* Holds if the function `func` is a classmethod -- either by having a
* `@classmethod` decorator or by convention
* (like a `__new__` method on a class is a classmethod even without the decorator).
*/
predicate isClassmethod(Function func) {
exists(NameNode id | id.getId() = "classmethod" and id.isGlobal() |
func.getADecorator() = id.getNode()
)
or
exists(Class cls |
cls.getAMethod() = func and
func.getName() = "__new__"
)
}
// =============================================================================
@@ -309,12 +322,12 @@ class DataFlowMethod extends DataFlowFunction {
/** A classmethod. */
class DataFlowClassmethod extends DataFlowMethod {
DataFlowClassmethod() { hasClassmethodDecorator(func) }
DataFlowClassmethod() { isClassmethod(func) }
}
/** A staticmethod. */
class DataFlowStaticmethod extends DataFlowMethod, DataFlowFunction {
DataFlowStaticmethod() { hasStaticmethodDecorator(func) }
DataFlowStaticmethod() { isStaticmethod(func) }
override int positionalOffset() { result = 0 }
@@ -457,8 +470,8 @@ private TypeTrackingNode selfTracker(TypeTracker t, Class classWithMethod) {
t.start() and
exists(Function func |
func = classWithMethod.getAMethod() and
not hasStaticmethodDecorator(func) and
not hasClassmethodDecorator(func)
not isStaticmethod(func) and
not isClassmethod(func)
|
result.asExpr() = func.getArg(0)
)
@@ -482,7 +495,7 @@ private TypeTrackingNode clsTracker(TypeTracker t, Class classWithMethod) {
(
exists(Function func |
func = classWithMethod.getAMethod() and
hasClassmethodDecorator(func)
isClassmethod(func)
|
result.asExpr() = func.getArg(0)
)
@@ -507,7 +520,7 @@ Node clsTracker(Class classWithMethod) {
* call happened in the method `func` (either a method or a classmethod).
*/
private TypeTrackingNode superCallNoArgumentTracker(TypeTracker t, Function func) {
not hasStaticmethodDecorator(func) and
not isStaticmethod(func) and
t.start() and
exists(CallCfgNode call | result = call |
call = getSuperCall() and
@@ -884,22 +897,22 @@ private module MethodCalls {
or
self = selfTracker(_)
) and
not hasStaticmethodDecorator(target) and
not hasClassmethodDecorator(target)
not isStaticmethod(target) and
not isClassmethod(target)
or
// method as plain function call
type instanceof CallTypeMethodAsPlainFunction and
self = classTracker(_) and
not hasStaticmethodDecorator(target) and
not hasClassmethodDecorator(target)
not isStaticmethod(target) and
not isClassmethod(target)
or
// staticmethod call
type instanceof CallTypeStaticMethod and
hasStaticmethodDecorator(target)
isStaticmethod(target)
or
// classmethod call
type instanceof CallTypeClassMethod and
hasClassmethodDecorator(target)
isClassmethod(target)
)
}
}

View File

@@ -63,7 +63,7 @@ newtype TNode =
// self parameter when used implicitly in `super()`
exists(Class cls, Function func, ParameterDefinition def |
func = cls.getAMethod() and
not hasStaticmethodDecorator(func) and
not isStaticmethod(func) and
// this matches what we do in ExtractedParameterNode
def.getDefiningNode() = node and
def.getParameter() = func.getArg(0)

View File

@@ -9,7 +9,7 @@
class WithNewImpl(object):
def __new__(cls):
print("WithNewImpl.foo")
cls.foo() # $ call=cls.foo() callType=CallTypeClassMethod MISSING: arg[self]=cls
cls.foo() # $ call=cls.foo() callType=CallTypeClassMethod arg[self]=cls
@classmethod
def foo(cls):