|
13 | 13 |
|
14 | 14 | import python |
15 | 15 | private import semmle.python.dataflow.new.internal.DataFlowDispatch |
| 16 | +private import semmle.python.ApiGraphs |
16 | 17 |
|
17 | | -from For loop, Expr iter, Class cls |
| 18 | +/** |
| 19 | + * Holds if `cls_arg` references a known iterable builtin type, either directly |
| 20 | + * (e.g. `list`) or as an element of a tuple (e.g. `(list, tuple)`). |
| 21 | + */ |
| 22 | +private predicate isIterableTypeArg(DataFlow::Node cls_arg) { |
| 23 | + cls_arg = |
| 24 | + API::builtin([ |
| 25 | + "list", "tuple", "set", "frozenset", "dict", "str", "bytes", "bytearray", "range", |
| 26 | + "memoryview" |
| 27 | + ]).getAValueReachableFromSource() |
| 28 | + or |
| 29 | + isIterableTypeArg(DataFlow::exprNode(cls_arg.asExpr().(Tuple).getAnElt())) |
| 30 | +} |
| 31 | + |
| 32 | +/** |
| 33 | + * Holds if `iter` is guarded by an `isinstance` check that tests for |
| 34 | + * an iterable type (e.g. `list`, `tuple`, `set`, `dict`). |
| 35 | + */ |
| 36 | +predicate guardedByIsinstanceIterable(DataFlow::Node iter) { |
| 37 | + exists( |
| 38 | + ConditionBlock guard, DataFlow::CallCfgNode isinstance_call, DataFlow::LocalSourceNode src |
| 39 | + | |
| 40 | + isinstance_call = API::builtin("isinstance").getACall() and |
| 41 | + src.flowsTo(isinstance_call.getArg(0)) and |
| 42 | + src.flowsTo(iter) and |
| 43 | + isIterableTypeArg(isinstance_call.getArg(1)) and |
| 44 | + guard.getLastNode() = isinstance_call.asCfgNode() and |
| 45 | + guard.controls(iter.asCfgNode().getBasicBlock(), true) |
| 46 | + ) |
| 47 | +} |
| 48 | + |
| 49 | +from For loop, DataFlow::Node iter, Class cls |
18 | 50 | where |
19 | | - iter = loop.getIter() and |
20 | | - classInstanceTracker(cls).asExpr() = iter and |
| 51 | + iter.asExpr() = loop.getIter() and |
| 52 | + iter = classInstanceTracker(cls) and |
21 | 53 | not DuckTyping::isIterable(cls) and |
22 | 54 | not DuckTyping::isDescriptor(cls) and |
23 | 55 | not (loop.isAsync() and DuckTyping::hasMethod(cls, "__aiter__")) and |
24 | | - not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls)) |
25 | | -select loop, "This for-loop may attempt to iterate over a $@ of class $@.", iter, |
| 56 | + not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls)) and |
| 57 | + not guardedByIsinstanceIterable(iter) |
| 58 | +select loop, "This for-loop may attempt to iterate over a $@ of class $@.", iter.asExpr(), |
26 | 59 | "non-iterable instance", cls, cls.getName() |
0 commit comments