Repository: numba/numba

Issue:
Double `while True` loop causes strange errors
Answering the following question on stackoverflow, I think I found a strange bug in Numba (using 0.39.0 on python 3.6.5):

https://stackoverflow.com/q/51795538/392949

There the code just gets stuck in an infinite loop, but a simpler example suffers from other strange errors.

A simple reproducible example. The following will throw a strange error:

```python
@nb.njit
def test(obs):
    L = obs.shape[0]
    i = 0
    a = 1
    b = 0
    
    while True:
        while True:
            if i == L - 1:
                break
            i += 1
        i += 1
        if i >= L:
            break

    return i, L
```

and running it:

```
obs = np.array([1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1], dtype=np.int64)

test(obs)
```

gives:

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-80c342766a9d> in <module>()
      1 obs = np.array([1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1], dtype=np.int64)
      2 
----> 3 test(obs)

TypeError: can't unbox array from PyObject into native value.  The object maybe of a different type
```

If you get rid of the array argument, you get a different error:

```
@nb.njit
def test():
    L = 10  #obs.shape[0]
    i = 0
    a = 1
    b = 0
    
    while True:
        while True:
            if i == L - 1:
                break
            i += 1
        i += 1
        if i >= L:
            break

    return i, L
```

and running:

```
test()
```

gives:

```
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-7-fbd55f77ab7c> in <module>()
----> 1 test()

RuntimeError: missing Environment
```



while the following (changing the second `while True` to `while a > b`), runs as expected:

```
@nb.njit
def test(obs):
    L = obs.shape[0]
    i = 0
    a = 1
    b = 0
    
    while True:
        while a > b:
            if i == L - 1:
                break
            i += 1
        i += 1
        if i >= L:
            break
    return i, L
```
Double `while True` loop causes strange errors
Answering the following question on stackoverflow, I think I found a strange bug in Numba (using 0.39.0 on python 3.6.5):

https://stackoverflow.com/q/51795538/392949

There the code just gets stuck in an infinite loop, but a simpler example suffers from other strange errors.

A simple reproducible example. The following will throw a strange error:

```python
@nb.njit
def test(obs):
    L = obs.shape[0]
    i = 0
    a = 1
    b = 0
    
    while True:
        while True:
            if i == L - 1:
                break
            i += 1
        i += 1
        if i >= L:
            break

    return i, L
```

and running it:

```
obs = np.array([1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1], dtype=np.int64)

test(obs)
```

gives:

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-80c342766a9d> in <module>()
      1 obs = np.array([1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1], dtype=np.int64)
      2 
----> 3 test(obs)

TypeError: can't unbox array from PyObject into native value.  The object maybe of a different type
```

If you get rid of the array argument, you get a different error:

```
@nb.njit
def test():
    L = 10  #obs.shape[0]
    i = 0
    a = 1
    b = 0
    
    while True:
        while True:
            if i == L - 1:
                break
            i += 1
        i += 1
        if i >= L:
            break

    return i, L
```

and running:

```
test()
```

gives:

```
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-7-fbd55f77ab7c> in <module>()
----> 1 test()

RuntimeError: missing Environment
```



while the following (changing the second `while True` to `while a > b`), runs as expected:

```
@nb.njit
def test(obs):
    L = obs.shape[0]
    i = 0
    a = 1
    b = 0
    
    while True:
        while a > b:
            if i == L - 1:
                break
            i += 1
        i += 1
        if i >= L:
            break
    return i, L
```


Discussion:
This is caused by a mishandling of the `POP_BLOCK` python bytecode when `while <constant>` is used.  Python will put a `JUMP` right before the `POP_BLOCK` causing numba to not seeing the `POP_BLOCK`.  This leads the later `break` jumping back into the inner-loop, causing numba to emit an infinite-loop with no side-effect, which is an undefined behavior to LLVM.  The optimizer in LLVM then messes up the function further causing bizarre error messages.
This is caused by a mishandling of the `POP_BLOCK` python bytecode when `while <constant>` is used.  Python will put a `JUMP` right before the `POP_BLOCK` causing numba to not seeing the `POP_BLOCK`.  This leads the later `break` jumping back into the inner-loop, causing numba to emit an infinite-loop with no side-effect, which is an undefined behavior to LLVM.  The optimizer in LLVM then messes up the function further causing bizarre error messages.

Resolving patch:
diff --git a/numba/interpreter.py b/numba/interpreter.py
--- a/numba/interpreter.py
+++ b/numba/interpreter.py
@@ -143,6 +143,12 @@ def _start_new_block(self, inst):
         # Get DFA block info
         self.dfainfo = self.dfa.infos[self.current_block_offset]
         self.assigner = Assigner()
+        # Check out-of-scope syntactic-block
+        while self.syntax_blocks:
+            if inst.offset >= self.syntax_blocks[-1].exit:
+                self.syntax_blocks.pop()
+            else:
+                break
 
     def _end_current_block(self):
         self._remove_unused_temporaries()


Test patch:
diff --git a/numba/tests/test_flow_control.py b/numba/tests/test_flow_control.py
--- a/numba/tests/test_flow_control.py
+++ b/numba/tests/test_flow_control.py
@@ -174,10 +174,26 @@ def ifelse_usecase4(x, y):
     if x == y:
         return 1
 
+
 def ternary_ifelse_usecase1(x, y):
     return True if x > y else False
 
 
+def double_infinite_loop(x, y):
+    L = x
+    i = y
+
+    while True:
+        while True:
+            if i == L - 1:
+                break
+            i += 1
+        i += 1
+        if i >= L:
+            break
+
+    return i, L
+
 class TestFlowControl(TestCase):
 
     def run_test(self, pyfunc, x_operands, y_operands,
@@ -347,6 +363,13 @@ def test_ternary_ifelse1(self, flags=enable_pyobj_flags):
     def test_ternary_ifelse1_npm(self):
         self.test_ternary_ifelse1(flags=no_pyobj_flags)
 
+    def test_double_infinite_loop(self, flags=enable_pyobj_flags):
+        self.run_test(double_infinite_loop, [10], [0],
+                      flags=flags)
+
+    def test_double_infinite_loop_npm(self):
+        self.test_double_infinite_loop(flags=no_pyobj_flags)
+
 
 class TestCFGraph(TestCase):
     """