|
1 | | -deprecated module; |
| 1 | +/** Definitions for reasoning about multiple or missing calls to superclass methods. */ |
2 | 2 |
|
3 | 3 | import python |
| 4 | +import semmle.python.ApiGraphs |
| 5 | +import semmle.python.dataflow.new.internal.DataFlowDispatch |
4 | 6 |
|
5 | 7 | // Helper predicates for multiple call to __init__/__del__ queries. |
6 | 8 | pragma[noinline] |
@@ -36,42 +38,77 @@ predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject m |
36 | 38 | ) |
37 | 39 | } |
38 | 40 |
|
39 | | -/** Holds if all attributes called `name` can be inferred to be methods. */ |
40 | | -private predicate named_attributes_not_method(ClassObject cls, string name) { |
41 | | - cls.declaresAttribute(name) and not cls.declaredAttribute(name) instanceof FunctionObject |
| 41 | +predicate nonTrivial(Function meth) { |
| 42 | + exists(Stmt s | s = meth.getAStmt() | |
| 43 | + not s instanceof Pass and |
| 44 | + not exists(DataFlow::Node call | call.asExpr() = s.(ExprStmt).getValue() | |
| 45 | + superCall(call, meth.getName()) |
| 46 | + or |
| 47 | + callsMethodOnClassWithSelf(meth, call, _, meth.getName()) |
| 48 | + ) |
| 49 | + ) and |
| 50 | + exists(meth.getANormalExit()) // doesn't always raise an exception |
42 | 51 | } |
43 | 52 |
|
44 | | -/** Holds if `f` actually does something. */ |
45 | | -private predicate does_something(FunctionObject f) { |
46 | | - f.isBuiltin() and not f = theObjectType().lookupAttribute("__init__") |
47 | | - or |
48 | | - exists(Stmt s | s = f.getFunction().getAStmt() and not s instanceof Pass) |
| 53 | +predicate superCall(DataFlow::MethodCallNode call, string name) { |
| 54 | + exists(DataFlow::Node sup | |
| 55 | + call.calls(sup, name) and |
| 56 | + sup = API::builtin("super").getACall() |
| 57 | + ) |
49 | 58 | } |
50 | 59 |
|
51 | | -/** Holds if `meth` looks like it should have a call to `name`, but does not */ |
52 | | -private predicate missing_call(FunctionObject meth, string name) { |
53 | | - exists(CallNode call, AttrNode attr | |
54 | | - call.getScope() = meth.getFunction() and |
55 | | - call.getFunction() = attr and |
56 | | - attr.getName() = name and |
57 | | - not exists(FunctionObject f | f.getACall() = call) |
| 60 | +predicate callsSuper(Function meth) { |
| 61 | + exists(DataFlow::MethodCallNode call | |
| 62 | + call.getScope() = meth and |
| 63 | + superCall(call, meth.getName()) |
58 | 64 | ) |
59 | 65 | } |
60 | 66 |
|
61 | | -/** Holds if `self.name` does not call `missing`, even though it is expected to. */ |
62 | | -predicate missing_call_to_superclass_method( |
63 | | - ClassObject self, FunctionObject top, FunctionObject missing, string name |
| 67 | +predicate callsMethodOnClassWithSelf( |
| 68 | + Function meth, DataFlow::MethodCallNode call, Class target, string name |
64 | 69 | ) { |
65 | | - missing = self.getASuperType().declaredAttribute(name) and |
66 | | - top = self.lookupAttribute(name) and |
67 | | - /* There is no call to missing originating from top */ |
68 | | - not top.getACallee*() = missing and |
69 | | - /* Make sure that all named 'methods' are objects that we can understand. */ |
70 | | - not exists(ClassObject sup | |
71 | | - sup = self.getAnImproperSuperType() and |
72 | | - named_attributes_not_method(sup, name) |
73 | | - ) and |
74 | | - not self.isAbstract() and |
75 | | - does_something(missing) and |
76 | | - not missing_call(top, name) |
| 70 | + exists(DataFlow::Node callTarget, DataFlow::ParameterNode self | |
| 71 | + call.calls(callTarget, name) and |
| 72 | + self.getParameter() = meth.getArg(0) and |
| 73 | + self.(DataFlow::LocalSourceNode).flowsTo(call.getArg(0)) and |
| 74 | + callTarget = classTracker(target) |
| 75 | + ) |
| 76 | +} |
| 77 | + |
| 78 | +predicate callsMethodOnUnknownClassWithSelf(Function meth, string name) { |
| 79 | + exists(DataFlow::MethodCallNode call, DataFlow::Node callTarget, DataFlow::ParameterNode self | |
| 80 | + call.calls(callTarget, name) and |
| 81 | + self.getParameter() = meth.getArg(0) and |
| 82 | + self.(DataFlow::LocalSourceNode).flowsTo(call.getArg(0)) and |
| 83 | + not exists(Class target | callTarget = classTracker(target)) |
| 84 | + ) |
| 85 | +} |
| 86 | + |
| 87 | +predicate mayProceedInMro(Class a, Class b, Class mroStart) { |
| 88 | + b = getNextClassInMroKnownStartingClass(a, mroStart) |
| 89 | + or |
| 90 | + exists(Class mid | |
| 91 | + mid = getNextClassInMroKnownStartingClass(a, mroStart) and |
| 92 | + mayProceedInMro(mid, b, mroStart) |
| 93 | + ) |
| 94 | +} |
| 95 | + |
| 96 | +predicate missingCallToSuperclassMethod( |
| 97 | + Function base, Function shouldCall, Class mroStart, string name |
| 98 | +) { |
| 99 | + base.getName() = name and |
| 100 | + shouldCall.getName() = name and |
| 101 | + not callsSuper(base) and |
| 102 | + not callsMethodOnUnknownClassWithSelf(base, name) and |
| 103 | + nonTrivial(shouldCall) and |
| 104 | + base.getScope() = getADirectSuperclass*(mroStart) and |
| 105 | + mayProceedInMro(base.getScope(), shouldCall.getScope(), mroStart) and |
| 106 | + not exists(Class called | |
| 107 | + ( |
| 108 | + callsMethodOnClassWithSelf(base, _, called, name) |
| 109 | + or |
| 110 | + callsMethodOnClassWithSelf(findFunctionAccordingToMro(mroStart, name), _, called, name) |
| 111 | + ) and |
| 112 | + shouldCall.getScope() = getADirectSuperclass*(called) |
| 113 | + ) |
77 | 114 | } |
0 commit comments