|
4 | 4 | * @kind problem
|
5 | 5 | * @tags reliability
|
6 | 6 | * correctness
|
| 7 | + * quality |
7 | 8 | * @problem.severity error
|
8 | 9 | * @sub-severity low
|
9 | 10 | * @precision high
|
10 | 11 | * @id py/iter-returns-non-self
|
11 | 12 | */
|
12 | 13 |
|
13 | 14 | import python
|
| 15 | +import semmle.python.dataflow.new.DataFlow |
| 16 | +import semmle.python.ApiGraphs |
14 | 17 |
|
15 |
| -Function iter_method(ClassValue t) { result = t.lookup("__iter__").(FunctionValue).getScope() } |
| 18 | +/** Holds if `f` is a method of the class `c`. */ |
| 19 | +private predicate methodOfClass(Function f, Class c) { |
| 20 | + exists(FunctionDef d | d.getDefinedFunction() = f and d.getScope() = c) |
| 21 | +} |
| 22 | + |
| 23 | +Function iterMethod(Class c) { methodOfClass(result, c) and result.getName() = "__iter__" } |
| 24 | + |
| 25 | +Function nextMethod(Class c) { methodOfClass(result, c) and result.getName() = "__next__" } |
16 | 26 |
|
17 |
| -predicate is_self(Name value, Function f) { value.getVariable() = f.getArg(0).(Name).getVariable() } |
| 27 | +predicate isSelfVar(Function f, Name var) { var.getVariable() = f.getArg(0).(Name).getVariable() } |
| 28 | + |
| 29 | +predicate isGoodReturn(Function f, Expr e) { |
| 30 | + isSelfVar(f, e) |
| 31 | + or |
| 32 | + exists(DataFlow::CallCfgNode call, DataFlow::AttrRead read, DataFlow::Node selfNode | |
| 33 | + e = call.asExpr() |
| 34 | + | |
| 35 | + call = API::builtin("iter").getACall() and |
| 36 | + call.getArg(0) = read and |
| 37 | + read.accesses(selfNode, "__next__") and |
| 38 | + isSelfVar(f, selfNode.asExpr()) and |
| 39 | + call.getArg(1).asExpr() instanceof None |
| 40 | + ) |
| 41 | +} |
18 | 42 |
|
19 |
| -predicate returns_non_self(Function f) { |
| 43 | +predicate returnsNonSelf(Function f) { |
20 | 44 | exists(f.getFallthroughNode())
|
21 | 45 | or
|
22 |
| - exists(Return r | r.getScope() = f and not is_self(r.getValue(), f)) |
| 46 | + exists(Return r | r.getScope() = f and not isGoodReturn(f, r.getValue())) |
23 | 47 | or
|
24 | 48 | exists(Return r | r.getScope() = f and not exists(r.getValue()))
|
25 | 49 | }
|
26 | 50 |
|
27 |
| -from ClassValue t, Function iter |
28 |
| -where t.isIterator() and iter = iter_method(t) and returns_non_self(iter) |
29 |
| -select t, "Class " + t.getName() + " is an iterator but its $@ method does not return 'self'.", |
30 |
| - iter, iter.getName() |
| 51 | +from Class c, Function iter |
| 52 | +where |
| 53 | + exists(nextMethod(c)) and |
| 54 | + iter = iterMethod(c) and |
| 55 | + returnsNonSelf(iter) |
| 56 | +select iter, "Iter method of iterator $@ does not return `" + iter.getArg(0).getName() + "`.", c, |
| 57 | + c.getName() |
0 commit comments