summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Behnel <stefan_ml@behnel.de>2023-04-01 20:52:13 +0200
committerStefan Behnel <stefan_ml@behnel.de>2023-04-01 23:55:20 +0200
commit4c42949b47eff1bce921fae71ed93ec359dd32a4 (patch)
tree754763ce9b56e7e13b6ced7a1b0cea0fe5dd1574
parentd0d1cde6ffa20ac823c857909692e285d731778c (diff)
downloadcython-4c42949b47eff1bce921fae71ed93ec359dd32a4.tar.gz
Fix a reference leak when a for-loop's "else:" branch contains a "break" for an outer loop.
Closes https://github.com/cython/cython/issues/5347
-rw-r--r--Cython/Compiler/Nodes.py23
-rw-r--r--tests/run/for_in_break_continue_T533.pyx29
2 files changed, 42 insertions, 10 deletions
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
index c5ddad0a6..457ae94ad 100644
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -6723,23 +6723,28 @@ class _ForInStatNode(LoopNode, StatNode):
code.set_loop_labels(old_loop_labels)
if self.else_clause:
- # in nested loops, the 'else' block can contain a
- # 'continue' statement for the outer loop, but we may need
- # to generate cleanup code before taking that path, so we
- # intercept it here
- orig_continue_label = code.continue_label
+ # In nested loops, the 'else' block can contain 'continue' or 'break'
+ # statements for the outer loop, but we may need to generate cleanup code
+ # before taking those paths, so we intercept them here.
+ orig_exit_labels = (code.continue_label, code.break_label)
code.continue_label = code.new_label('outer_continue')
+ code.break_label = code.new_label('outer_break')
code.putln("/*else*/ {")
self.else_clause.generate_execution_code(code)
code.putln("}")
- if code.label_used(code.continue_label):
- code.put_goto(break_label)
+ needs_goto_end = not self.else_clause.is_terminator
+ for exit_label, orig_exit_label in zip([code.continue_label, code.break_label], orig_exit_labels):
+ if not code.label_used(exit_label):
+ continue
+ if needs_goto_end:
+ code.put_goto(break_label)
+ needs_goto_end = False
code.mark_pos(self.pos)
- code.put_label(code.continue_label)
+ code.put_label(exit_label)
self.iterator.generate_disposal_code(code)
- code.put_goto(orig_continue_label)
+ code.put_goto(orig_exit_label)
code.set_loop_labels(old_loop_labels)
code.mark_pos(self.pos)
diff --git a/tests/run/for_in_break_continue_T533.pyx b/tests/run/for_in_break_continue_T533.pyx
index 0baa9fa49..20dfa4caa 100644
--- a/tests/run/for_in_break_continue_T533.pyx
+++ b/tests/run/for_in_break_continue_T533.pyx
@@ -1,4 +1,6 @@
-# ticket: 533
+# mode: run
+# ticket: t533
+# ticket: gh1093
def for_in():
"""
@@ -20,6 +22,7 @@ def for_in():
break
return i
+
def for_from():
"""
>>> for_from()
@@ -39,3 +42,27 @@ def for_from():
print "BREAK", i
break
return i
+
+
+def for_in_break2(data, avoid):
+ """
+ >>> for_in_break2([1,2,3,None], avoid=[1,2,3])
+ 3
+ >>> for_in_break2([1,2,3,None], avoid=[1])
+ 1
+ >>> for_in_break2([1,2,3,None], avoid=[1])
+ 1
+ """
+ data_iter = iter(data)
+ value = None
+ while 1:
+ match = next(data_iter)
+ if match is None:
+ break
+ for value in avoid:
+ if match == value:
+ break
+ else:
+ break
+
+ return value