-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathIterReturnsNonSelf.ql
More file actions
90 lines (80 loc) · 2.94 KB
/
IterReturnsNonSelf.ql
File metadata and controls
90 lines (80 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* @name Iterator does not return self from `__iter__` method
* @description Iterator does not return self from `__iter__` method, violating the iterator protocol.
* @kind problem
* @tags reliability
* correctness
* quality
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/iter-returns-non-self
*/
import python
import semmle.python.ApiGraphs
/** Gets the __iter__ method of `c`. */
Function iterMethod(Class c) { result = c.getAMethod() and result.getName() = "__iter__" }
/** Gets the `__next__` method of `c`. */
Function nextMethod(Class c) { result = c.getAMethod() and result.getName() = "__next__" }
/** Holds if `var` is a variable referring to the `self` parameter of `f`. */
predicate isSelfVar(Function f, Name var) { var.getVariable() = f.getArg(0).(Name).getVariable() }
/** Holds if `e` is an expression that an iter function `f` should return. */
predicate isGoodReturn(Function f, Expr e) {
isSelfVar(f, e)
or
exists(DataFlow::CallCfgNode call, DataFlow::AttrRead read, DataFlow::Node selfNode |
e = call.asExpr()
|
call = API::builtin("iter").getACall() and
call.getArg(0) = read and
read.accesses(selfNode, "__next__") and
isSelfVar(f, selfNode.asExpr()) and
call.getArg(1).asExpr() instanceof None
)
}
/** Holds if the iter method `f` does not return `self` or an equivalent. */
predicate returnsNonSelf(Function f) {
exists(f.getFallthroughNode())
or
exists(Return r | r.getScope() = f and not isGoodReturn(f, r.getValue()))
}
/** Holds if `iter` and `next` methods are wrappers around some field. */
predicate iterWrapperMethods(Function iter, Function next) {
exists(string field |
exists(Return r, DataFlow::Node self, DataFlow::AttrRead read |
r.getScope() = iter and
r.getValue() = [iterCall(read).asExpr(), read.asExpr()] and
read.accesses(self, field) and
isSelfVar(iter, self.asExpr())
) and
exists(Return r, DataFlow::Node self, DataFlow::AttrRead read |
r.getScope() = next and
r.getValue() = nextCall(read).asExpr() and
read.accesses(self, field) and
isSelfVar(next, self.asExpr())
)
)
}
/** Gets a call to `iter(arg)` or `arg.__iter__()`. */
private DataFlow::CallCfgNode iterCall(DataFlow::Node arg) {
result.(DataFlow::MethodCallNode).calls(arg, "__iter__")
or
result = API::builtin("iter").getACall() and
arg = result.getArg(0) and
not exists(result.getArg(1))
}
/** Gets a call to `next(arg)` or `arg.__next__()`. */
private DataFlow::CallCfgNode nextCall(DataFlow::Node arg) {
result.(DataFlow::MethodCallNode).calls(arg, "__next__")
or
result = API::builtin("next").getACall() and
arg = result.getArg(0)
}
from Class c, Function iter, Function next
where
next = nextMethod(c) and
iter = iterMethod(c) and
returnsNonSelf(iter) and
not iterWrapperMethods(iter, next)
select iter, "Iter method of iterator $@ does not return `" + iter.getArg(0).getName() + "`.", c,
c.getName()