Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Python: Port NonIteratorInForLoop.ql
Uses the `DuckTyping` module to approximate whether something is likely
to be an iterator or not.

We lose one test result due to the fact that we don't know what to do
about `for ... in 1` (because `1` is an instance of a built-in). I'm
going to defer addressing this until we get some modelling of built-in
types.
  • Loading branch information
tausbn committed Apr 9, 2026
commit 32c4db4e50f4ff3b51057dd53bb053279b6eef45
21 changes: 10 additions & 11 deletions python/ql/src/Statements/NonIteratorInForLoop.ql
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch

from For loop, ControlFlowNodeWithPointsTo iter, Value v, ClassValue t, ControlFlowNode origin
from For loop, Expr iter, Class cls
where
loop.getIter().getAFlowNode() = iter and
iter.pointsTo(_, v, origin) and
v.getClass() = t and
not t.isIterable() and
not t.failedInference(_) and
not v = Value::named("None") and
not t.isDescriptorType()
select loop, "This for-loop may attempt to iterate over a $@ of class $@.", origin,
"non-iterable instance", t, t.getName()
iter = loop.getIter() and
classInstanceTracker(cls).asExpr() = iter and
not DuckTyping::isIterable(cls) and
not DuckTyping::isDescriptor(cls) and
not (loop.isAsync() and DuckTyping::hasMethod(cls, "__aiter__")) and
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(cls))
select loop, "This for-loop may attempt to iterate over a $@ of class $@.", iter,
"non-iterable instance", cls, cls.getName()
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
| async_iterator.py:26:11:26:34 | For | This for-loop may attempt to iterate over a $@ of class $@. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | non-iterable instance | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter |
| statements_test.py:34:5:34:19 | For | This for-loop may attempt to iterate over a $@ of class $@. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | non-iterable instance | file://:0:0:0:0 | builtin-class int | int |
| async_iterator.py:26:11:26:34 | For | This for-loop may attempt to iterate over a $@ of class $@. | async_iterator.py:26:20:26:33 | MissingAiter() | non-iterable instance | async_iterator.py:13:1:13:19 | Class MissingAiter | MissingAiter |
Original file line number Diff line number Diff line change
@@ -1 +1 @@
| test.py:50:1:50:23 | For | This for-loop may attempt to iterate over a $@ of class $@. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | non-iterable instance | test.py:45:1:45:26 | class NonIterator | NonIterator |
| test.py:50:1:50:23 | For | This for-loop may attempt to iterate over a $@ of class $@. | test.py:50:10:50:22 | NonIterator() | non-iterable instance | test.py:45:1:45:26 | Class NonIterator | NonIterator |