Repository: numba/numba

Issue:
LoweringError: Failed in nopython mode pipeline (raise/assert related, caching issue?)
Numba: 0.43.1
Python: 3.7.0
Numpy: 1.16.3

I have run into a strange issue, unfortunately I cannot produce a minimal reproducing example, because the bug appears somehow related to a caching/transient effect.

Namely, I have a somewhat long user function

```
@jit(nopython=True)
def user_function(many_args):
    # lots
```

which produces such an error as shown below, but works fine if I turn off the JIT.

```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/.local/lib/python3.7/site-packages/numba/errors.py in new_error_context(fmt_, *args, **kwargs)
    626     try:
--> 627         yield
    628     except NumbaError as e:

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_block(self, block)
    257                                    loc=self.loc, errcls_=defaulterrcls):
--> 258                 self.lower_inst(inst)
    259

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_inst(self, inst)
    399         elif isinstance(inst, ir.StaticRaise):
--> 400             self.lower_static_raise(inst)
    401

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_static_raise(self, inst)
    441         else:
--> 442             self.return_exception(inst.exc_class, inst.exc_args, loc=self.loc)
    443

~/.local/lib/python3.7/site-packages/numba/lowering.py in return_exception(self, exc_class, exc_args, loc)
    162                                        loc=loc,
--> 163                                        func_name=self.func_ir.func_id.func_name)
    164

~/.local/lib/python3.7/site-packages/numba/targets/callconv.py in return_user_exc(self, builder, exc, exc_args, loc, func_name)
    364         if loc is not None:
--> 365             fname = loc._raw_function_name()
    366             if fname is None:

~/.local/lib/python3.7/site-packages/numba/ir.py in _raw_function_name(self)
     62         if defn:
---> 63             return self._defmatcher.match(defn.strip()).groups()[0]
     64         else:

AttributeError: 'NoneType' object has no attribute 'groups'

During handling of the above exception, another exception occurred:

LoweringError                             Traceback (most recent call last)
<ipython-input-1-647b0e2d4c3b> in <module>
----> 1 userland stops at function call

~/.local/lib/python3.7/site-packages/numba/dispatcher.py in _compile_for_args(self, *args, **kws)
    367                     e.patch_message(''.join(e.args) + help_msg)
    368             # ignore the FULL_TRACEBACKS config, this needs reporting!
--> 369             raise e
    370
    371     def inspect_llvm(self, signature=None):

~/.local/lib/python3.7/site-packages/numba/dispatcher.py in _compile_for_args(self, *args, **kws)
    324                 argtypes.append(self.typeof_pyval(a))
    325         try:
--> 326             return self.compile(tuple(argtypes))
    327         except errors.TypingError as e:
    328             # Intercept typing error that may be due to an argument

~/.local/lib/python3.7/site-packages/numba/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     30         def _acquire_compile_lock(*args, **kwargs):
     31             with self:
---> 32                 return func(*args, **kwargs)
     33         return _acquire_compile_lock
     34

~/.local/lib/python3.7/site-packages/numba/dispatcher.py in compile(self, sig)
    656
    657             self._cache_misses[sig] += 1
--> 658             cres = self._compiler.compile(args, return_type)
    659             self.add_overload(cres)
    660             self._cache.save_overload(sig, cres)

~/.local/lib/python3.7/site-packages/numba/dispatcher.py in compile(self, args, return_type)
     80                                       args=args, return_type=return_type,
     81                                       flags=flags, locals=self.locals,
---> 82                                       pipeline_class=self.pipeline_class)
     83         # Check typing error if object mode is used
     84         if cres.typing_error is not None and not flags.enable_pyobject:

~/.local/lib/python3.7/site-packages/numba/compiler.py in compile_extra(typingctx, targetctx, func, args, return_type, flags, locals, library, pipeline_class)
    939     pipeline = pipeline_class(typingctx, targetctx, library,
    940                               args, return_type, flags, locals)
--> 941     return pipeline.compile_extra(func)
    942
    943

~/.local/lib/python3.7/site-packages/numba/compiler.py in compile_extra(self, func)
    370         self.lifted = ()
    371         self.lifted_from = None
--> 372         return self._compile_bytecode()
    373
    374     def compile_ir(self, func_ir, lifted=(), lifted_from=None):

~/.local/lib/python3.7/site-packages/numba/compiler.py in _compile_bytecode(self)
    870         """
    871         assert self.func_ir is None
--> 872         return self._compile_core()
    873
    874     def _compile_ir(self):

~/.local/lib/python3.7/site-packages/numba/compiler.py in _compile_core(self)
    857         self.define_pipelines(pm)
    858         pm.finalize()
--> 859         res = pm.run(self.status)
    860         if res is not None:
    861             # Early pipeline completion

~/.local/lib/python3.7/site-packages/numba/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     30         def _acquire_compile_lock(*args, **kwargs):
     31             with self:
---> 32                 return func(*args, **kwargs)
     33         return _acquire_compile_lock
     34

~/.local/lib/python3.7/site-packages/numba/compiler.py in run(self, status)
    251                     # No more fallback pipelines?
    252                     if is_final_pipeline:
--> 253                         raise patched_exception
    254                     # Go to next fallback pipeline
    255                     else:

~/.local/lib/python3.7/site-packages/numba/compiler.py in run(self, status)
    242                 try:
    243                     event(stage_name)
--> 244                     stage()
    245                 except _EarlyPipelineCompletion as e:
    246                     return e.result

~/.local/lib/python3.7/site-packages/numba/compiler.py in stage_nopython_backend(self)
    729         """
    730         lowerfn = self.backend_nopython_mode
--> 731         self._backend(lowerfn, objectmode=False)
    732
    733     def stage_compile_interp_mode(self):

~/.local/lib/python3.7/site-packages/numba/compiler.py in _backend(self, lowerfn, objectmode)
    679             self.library.enable_object_caching()
    680
--> 681         lowered = lowerfn()
    682         signature = typing.signature(self.return_type, *self.args)
    683         self.cr = compile_result(

~/.local/lib/python3.7/site-packages/numba/compiler.py in backend_nopython_mode(self)
    666                 self.calltypes,
    667                 self.flags,
--> 668                 self.metadata)
    669
    670     def _backend(self, lowerfn, objectmode):

~/.local/lib/python3.7/site-packages/numba/compiler.py in native_lowering_stage(targetctx, library, interp, typemap, restype, calltypes, flags, metadata)
   1061         lower = lowering.Lower(targetctx, library, fndesc, interp,
   1062                                metadata=metadata)
-> 1063         lower.lower()
   1064         if not flags.no_cpython_wrapper:
   1065             lower.create_cpython_wrapper(flags.release_gil)

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower(self)
    175         if self.generator_info is None:
    176             self.genlower = None
--> 177             self.lower_normal_function(self.fndesc)
    178         else:
    179             self.genlower = self.GeneratorLower(self)

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_normal_function(self, fndesc)
    216         # Init argument values
    217         self.extract_function_arguments()
--> 218         entry_block_tail = self.lower_function_body()
    219
    220         # Close tail of entry block

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_function_body(self)
    241             bb = self.blkmap[offset]
    242             self.builder.position_at_end(bb)
--> 243             self.lower_block(block)
    244
    245         self.post_lower()

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_block(self, block)
    256             with new_error_context('lowering "{inst}" at {loc}', inst=inst,
    257                                    loc=self.loc, errcls_=defaulterrcls):
--> 258                 self.lower_inst(inst)
    259
    260     def create_cpython_wrapper(self, release_gil=False):

~/.local/envs/dylan3/lib/python3.7/contextlib.py in __exit__(self, type, value, traceback)
    128                 value = type()
    129             try:
--> 130                 self.gen.throw(type, value, traceback)
    131             except StopIteration as exc:
    132                 # Suppress StopIteration *unless* it's the same exception that

~/.local/lib/python3.7/site-packages/numba/errors.py in new_error_context(fmt_, *args, **kwargs)
    633         from numba import config
    634         tb = sys.exc_info()[2] if config.FULL_TRACEBACKS else None
--> 635         six.reraise(type(newerr), newerr, tb)
    636
    637

~/.local/lib/python3.7/site-packages/numba/six.py in reraise(tp, value, tb)
    657         if value.__traceback__ is not tb:
    658             raise value.with_traceback(tb)
--> 659         raise value
    660
    661 else:

LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
'NoneType' object has no attribute 'groups'

File "../../../u/dnelson/python/util/sub.py", line 2566:
    #def get_random_number():
        <source elided>
        if count != num:
            raise Exception('Mismatch.')
            ^

[1] During: lowering "raise <class 'Exception'>('Mismatch.')" at /u/dnelson/python/util/subfind.py (2566)
-------------------------------------------------------------------------------
This should not have happened, a problem has occurred in Numba's internals.
```

What I cannot understand is this:

If I copy the entire function, paste it just below, and rename, then numba compiles it fine, its runs successfully, and also subsequent calls run ok. However, if I then exit python and restart the interpreter, the original error reappears (with the renamed function, or the original function). Furthermore, the original trick (copying and renaming the function) then no longer works.

The line it throws the error on is valid of course (the `raise Exception` works fine in JITed code). If I remove this, the error moves to other parts of the function, always `assert` or `raise` related. I realize now, if I remove every single such statement from the function, then the error goes away.

It is possible this behavior was introduced between 0.41 and 0.43, as it seems to come about when I did this upgrade, but I am not positive.


Discussion:
Thanks for the report. Without a reproducer this may be difficult to diagnose, however I can speculate... it looks like the problem is here:
```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/.local/lib/python3.7/site-packages/numba/errors.py in new_error_context(fmt_, *args, **kwargs)
    626     try:
--> 627         yield
    628     except NumbaError as e:

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_block(self, block)
    257                                    loc=self.loc, errcls_=defaulterrcls):
--> 258                 self.lower_inst(inst)
    259

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_inst(self, inst)
    399         elif isinstance(inst, ir.StaticRaise):
--> 400             self.lower_static_raise(inst)
    401

~/.local/lib/python3.7/site-packages/numba/lowering.py in lower_static_raise(self, inst)
    441         else:
--> 442             self.return_exception(inst.exc_class, inst.exc_args, loc=self.loc)
    443

~/.local/lib/python3.7/site-packages/numba/lowering.py in return_exception(self, exc_class, exc_args, loc)
    162                                        loc=loc,
--> 163                                        func_name=self.func_ir.func_id.func_name)
    164

~/.local/lib/python3.7/site-packages/numba/targets/callconv.py in return_user_exc(self, builder, exc, exc_args, loc, func_name)
    364         if loc is not None:
--> 365             fname = loc._raw_function_name()
    366             if fname is None:

~/.local/lib/python3.7/site-packages/numba/ir.py in _raw_function_name(self)
     62         if defn:
---> 63             return self._defmatcher.match(defn.strip()).groups()[0]
     64         else:

AttributeError: 'NoneType' object has no attribute 'groups'

```
Since 0.41, Numba has had support for exception locations in exceptions raised from compiled code, these are baked in at compile time. What is happening is that `lower_static_raise` is being invoked to create a representation of an exception in the compiled code and `linecache` is producing lines that do contain a `def:` but for some reason they aren't being matched.

This has puzzled me for some 20 minutes now but I now think I understand what might be causing this looking into the backtrace given, this is present...
```
    #def get_random_number():
        <source elided>
        if count != num:
            raise Exception('Mismatch.')
```
when the linecache source is walked looking for a function name to use in the synthesized backtrace, first a cheap match is done for "is there a `'def '` present in a given source line", the commented out line matches this and is returned as the candidate for the function name, this then doesn't match the more expensive regex pattern which tries to extract the function name from the source line because there's a `#` commenting out the line. I'm not sure that this is the sole cause of the problems you are seeing, but just for now please could you try removing that commented `'def'` line and seeing if the code behaves again, if so I'll go fix it :)

Reproducer:
```python
from numba import njit

def foo():
    #def commented_definition()
    raise Exception('aaa')

z = njit(foo)
z()
```
gives:
```
    return self._defmatcher.match(defn.strip()).groups()[0]
numba.errors.LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
'NoneType' object has no attribute 'groups'

File "issue4056.py", line 5:
    #def commented_definition()
    raise Exception('aaa')
    ^

[1] During: lowering "raise <class 'Exception'>('aaa')" at issue4056.py (5)
```
You are correct, the user function has a commented out, nested function, from an earlier development phase `#def get_random_number():` and removing this solves the issue. Thanks!
@dnelson86 thanks for confirming, we'll fix this!
Reminder to fix for 0.44.

Resolving patch:
diff --git a/numba/ir.py b/numba/ir.py
--- a/numba/ir.py
+++ b/numba/ir.py
@@ -59,7 +59,9 @@ def _find_definition(self):
         fn_name = None
         lines = self.get_lines()
         for x in reversed(lines[:self.line - 1]):
-            if 'def ' in x:
+            # the strip and startswith is to handle user code with commented out
+            # 'def' or use of 'def' in a docstring.
+            if x.strip().startswith('def '):
                 fn_name = x
                 break
 


Test patch:
diff --git a/numba/tests/test_errorhandling.py b/numba/tests/test_errorhandling.py
--- a/numba/tests/test_errorhandling.py
+++ b/numba/tests/test_errorhandling.py
@@ -75,6 +75,26 @@ def fn(x):
         a = np.array([1.0],dtype=np.float64)
         fn(a) # should not raise
 
+    def test_commented_func_definition_is_not_a_definition(self):
+        # See issue #4056, the commented def should not be found as the
+        # definition for reporting purposes when creating the synthetic
+        # traceback because it is commented! Use of def in docstring would also
+        # cause this issue hence is tested.
+
+        def foo_commented():
+            #def commented_definition()
+            raise Exception('test_string')
+
+        def foo_docstring():
+            """ def docstring containing def might match function definition!"""
+            raise Exception('test_string')
+
+        for func in (foo_commented, foo_docstring):
+            with self.assertRaises(Exception) as raises:
+                func()
+
+            self.assertIn("test_string", str(raises.exception))
+
     def test_use_of_ir_unknown_loc(self):
         # for context see # 3390
         import numba