Skip to content

Commit 7965a63

Browse files
Fix infinite loop when breaking from nested for loop
1 parent fcea845 commit 7965a63

File tree

3 files changed

+72
-50
lines changed

3 files changed

+72
-50
lines changed

tests/snippets/control_flow.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
def foo():
2+
sum = 0
3+
for i in range(10):
4+
sum += i
5+
for j in range(10):
6+
sum += j
7+
break
8+
return sum
9+
10+
assert foo() == 45

vm/src/compile.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,11 @@ impl Compiler {
234234
target: start_label,
235235
});
236236
self.set_label(else_label);
237+
self.emit(Instruction::PopBlock);
237238
if let Some(orelse) = orelse {
238239
self.compile_statements(orelse)?;
239240
}
240241
self.set_label(end_label);
241-
self.emit(Instruction::PopBlock);
242242
}
243243
ast::Statement::With { items, body } => {
244244
let end_label = self.new_label();
@@ -267,14 +267,6 @@ impl Compiler {
267267
body,
268268
orelse,
269269
} => {
270-
// The thing iterated:
271-
for i in iter {
272-
self.compile_expression(i)?;
273-
}
274-
275-
// Retrieve iterator
276-
self.emit(Instruction::GetIter);
277-
278270
// Start loop
279271
let start_label = self.new_label();
280272
let else_label = self.new_label();
@@ -283,6 +275,15 @@ impl Compiler {
283275
start: start_label,
284276
end: end_label,
285277
});
278+
279+
// The thing iterated:
280+
for i in iter {
281+
self.compile_expression(i)?;
282+
}
283+
284+
// Retrieve Iterator
285+
self.emit(Instruction::GetIter);
286+
286287
self.set_label(start_label);
287288
self.emit(Instruction::ForIter { target: else_label });
288289

@@ -295,11 +296,11 @@ impl Compiler {
295296
target: start_label,
296297
});
297298
self.set_label(else_label);
299+
self.emit(Instruction::PopBlock);
298300
if let Some(orelse) = orelse {
299301
self.compile_statements(orelse)?;
300302
}
301303
self.set_label(end_label);
302-
self.emit(Instruction::PopBlock);
303304
}
304305
ast::Statement::Raise { exception, cause } => match exception {
305306
Some(value) => {

vm/src/frame.rs

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@ use super::vm::VirtualMachine;
2323
use num_bigint::BigInt;
2424

2525
#[derive(Clone, Debug)]
26-
enum Block {
26+
struct Block {
27+
/// The type of block.
28+
typ: BlockType,
29+
/// The level of the value stack when the block was entered.
30+
level: usize,
31+
}
32+
33+
#[derive(Clone, Debug)]
34+
enum BlockType {
2735
Loop {
2836
start: bytecode::Label,
2937
end: bytecode::Label,
@@ -357,21 +365,21 @@ impl Frame {
357365
}
358366
}
359367
bytecode::Instruction::SetupLoop { start, end } => {
360-
self.push_block(Block::Loop {
368+
self.push_block(BlockType::Loop {
361369
start: *start,
362370
end: *end,
363371
});
364372
Ok(None)
365373
}
366374
bytecode::Instruction::SetupExcept { handler } => {
367-
self.push_block(Block::TryExcept { handler: *handler });
375+
self.push_block(BlockType::TryExcept { handler: *handler });
368376
Ok(None)
369377
}
370378
bytecode::Instruction::SetupWith { end } => {
371379
let context_manager = self.pop_value();
372380
// Call enter:
373381
let obj = vm.call_method(&context_manager, "__enter__", vec![])?;
374-
self.push_block(Block::With {
382+
self.push_block(BlockType::With {
375383
end: *end,
376384
context_manager: context_manager.clone(),
377385
});
@@ -380,22 +388,23 @@ impl Frame {
380388
}
381389
bytecode::Instruction::CleanupWith { end: end1 } => {
382390
let block = self.pop_block().unwrap();
383-
if let Block::With {
391+
if let BlockType::With {
384392
end: end2,
385393
context_manager,
386-
} = &block
394+
} = &block.typ
387395
{
388-
assert!(end1 == end2);
396+
debug_assert!(end1 == end2);
389397

390398
// call exit now with no exception:
391-
self.with_exit(vm, context_manager, None)?;
392-
Ok(None)
399+
self.with_exit(vm, &context_manager, None)?;
393400
} else {
394-
panic!("Block stack is incorrect, expected a with block");
401+
unreachable!("Block stack is incorrect, expected a with block");
395402
}
403+
404+
Ok(None)
396405
}
397406
bytecode::Instruction::PopBlock => {
398-
self.pop_block();
407+
self.pop_block().expect("no pop to block");
399408
Ok(None)
400409
}
401410
bytecode::Instruction::GetIter => {
@@ -532,7 +541,7 @@ impl Frame {
532541

533542
bytecode::Instruction::Break => {
534543
let block = self.unwind_loop(vm);
535-
if let Block::Loop { end, .. } = block {
544+
if let BlockType::Loop { end, .. } = block.typ {
536545
self.jump(end);
537546
}
538547
Ok(None)
@@ -543,7 +552,7 @@ impl Frame {
543552
}
544553
bytecode::Instruction::Continue => {
545554
let block = self.unwind_loop(vm);
546-
if let Block::Loop { start, .. } = block {
555+
if let BlockType::Loop { start, .. } = block.typ {
547556
self.jump(start);
548557
} else {
549558
assert!(false);
@@ -711,16 +720,15 @@ impl Frame {
711720

712721
// Unwind all blocks:
713722
fn unwind_blocks(&mut self, vm: &mut VirtualMachine) -> Option<PyObjectRef> {
714-
loop {
715-
let block = self.pop_block();
716-
match block {
717-
Some(Block::Loop { .. }) => {}
718-
Some(Block::TryExcept { .. }) => {
723+
while let Some(block) = self.pop_block() {
724+
match block.typ {
725+
BlockType::Loop { .. } => {}
726+
BlockType::TryExcept { .. } => {
719727
// TODO: execute finally handler
720728
}
721-
Some(Block::With {
729+
BlockType::With {
722730
context_manager, ..
723-
}) => {
731+
} => {
724732
match self.with_exit(vm, &context_manager, None) {
725733
Ok(..) => {}
726734
Err(exc) => {
@@ -729,28 +737,28 @@ impl Frame {
729737
}
730738
}
731739
}
732-
None => break None,
733740
}
734741
}
742+
743+
None
735744
}
736745

737746
fn unwind_loop(&mut self, vm: &mut VirtualMachine) -> Block {
738747
loop {
739-
let block = self.pop_block();
740-
match block {
741-
Some(Block::Loop { .. }) => break block.unwrap(),
742-
Some(Block::TryExcept { .. }) => {
748+
let block = self.pop_block().expect("not in a loop");
749+
match block.typ {
750+
BlockType::Loop { .. } => break block,
751+
BlockType::TryExcept { .. } => {
743752
// TODO: execute finally handler
744753
}
745-
Some(Block::With {
754+
BlockType::With {
746755
context_manager, ..
747-
}) => match self.with_exit(vm, &context_manager, None) {
756+
} => match self.with_exit(vm, &context_manager, None) {
748757
Ok(..) => {}
749758
Err(exc) => {
750759
panic!("Exception in with __exit__ {:?}", exc);
751760
}
752761
},
753-
None => panic!("No block to break / continue"),
754762
}
755763
}
756764
}
@@ -761,18 +769,17 @@ impl Frame {
761769
exc: PyObjectRef,
762770
) -> Option<PyObjectRef> {
763771
// unwind block stack on exception and find any handlers:
764-
loop {
765-
let block = self.pop_block();
766-
match block {
767-
Some(Block::TryExcept { handler }) => {
772+
while let Some(block) = self.pop_block() {
773+
match block.typ {
774+
BlockType::TryExcept { handler } => {
768775
self.push_value(exc);
769776
self.jump(handler);
770777
return None;
771778
}
772-
Some(Block::With {
779+
BlockType::With {
773780
end,
774781
context_manager,
775-
}) => {
782+
} => {
776783
match self.with_exit(vm, &context_manager, Some(exc.clone())) {
777784
Ok(exit_action) => {
778785
match objbool::boolval(vm, exit_action) {
@@ -797,15 +804,14 @@ impl Frame {
797804
}
798805
}
799806
}
800-
Some(Block::Loop { .. }) => {}
801-
None => break,
807+
BlockType::Loop { .. } => {}
802808
}
803809
}
804810
Some(exc)
805811
}
806812

807813
fn with_exit(
808-
&mut self,
814+
&self,
809815
vm: &mut VirtualMachine,
810816
context_manager: &PyObjectRef,
811817
exc: Option<PyObjectRef>,
@@ -1061,12 +1067,17 @@ impl Frame {
10611067
self.code.locations[self.lasti].clone()
10621068
}
10631069

1064-
fn push_block(&mut self, block: Block) {
1065-
self.blocks.push(block);
1070+
fn push_block(&mut self, typ: BlockType) {
1071+
self.blocks.push(Block {
1072+
typ,
1073+
level: self.stack.len(),
1074+
});
10661075
}
10671076

10681077
fn pop_block(&mut self) -> Option<Block> {
1069-
self.blocks.pop()
1078+
let block = self.blocks.pop()?;
1079+
self.stack.truncate(block.level);
1080+
Some(block)
10701081
}
10711082

10721083
pub fn push_value(&mut self, obj: PyObjectRef) {

0 commit comments

Comments
 (0)