|
| 1 | +deprecated module; |
| 2 | + |
| 3 | +import python |
| 4 | + |
| 5 | +// Helper predicates for multiple call to __init__/__del__ queries. |
| 6 | +pragma[noinline] |
| 7 | +private predicate multiple_invocation_paths_helper( |
| 8 | + FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi |
| 9 | +) { |
| 10 | + i1 != i2 and |
| 11 | + i1 = top.getACallee+() and |
| 12 | + i2 = top.getACallee+() and |
| 13 | + i1.getFunction() = multi |
| 14 | +} |
| 15 | + |
| 16 | +pragma[noinline] |
| 17 | +private predicate multiple_invocation_paths( |
| 18 | + FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi |
| 19 | +) { |
| 20 | + multiple_invocation_paths_helper(top, i1, i2, multi) and |
| 21 | + i2.getFunction() = multi |
| 22 | +} |
| 23 | + |
| 24 | +/** Holds if `self.name` calls `multi` by multiple paths, and thus calls it more than once. */ |
| 25 | +predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject multi, string name) { |
| 26 | + exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 | |
| 27 | + multiple_invocation_paths(top, i1, i2, multi) and |
| 28 | + top.runtime(self.declaredAttribute(name)) and |
| 29 | + self.getASuperType().declaredAttribute(name) = multi |
| 30 | + | |
| 31 | + // Only called twice if called from different functions, |
| 32 | + // or if one call-site can reach the other. |
| 33 | + i1.getCall().getScope() != i2.getCall().getScope() |
| 34 | + or |
| 35 | + i1.getCall().strictlyReaches(i2.getCall()) |
| 36 | + ) |
| 37 | +} |
| 38 | + |
| 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 |
| 42 | +} |
| 43 | + |
| 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) |
| 49 | +} |
| 50 | + |
| 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) |
| 58 | + ) |
| 59 | +} |
| 60 | + |
| 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 |
| 64 | +) { |
| 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) |
| 77 | +} |
0 commit comments