Skip to content

Commit

Permalink
Merge pull request #5912 from stuartarchibald/fix/5844
Browse files Browse the repository at this point in the history
Fix bad attr access on certain typing templates breaking exceptions.
  • Loading branch information
sklam committed Jun 24, 2020
1 parent 4a71907 commit b945a8a
Show file tree
Hide file tree
Showing 6 changed files with 383 additions and 85 deletions.
1 change: 0 additions & 1 deletion .flake8
Expand Up @@ -305,7 +305,6 @@ exclude =
numba/tests/test_nested_calls.py
numba/tests/test_chained_assign.py
numba/tests/test_withlifting.py
numba/tests/test_errorhandling.py
numba/tests/test_parfors.py
numba/tests/test_sets.py
numba/tests/test_dyn_array.py
Expand Down
127 changes: 73 additions & 54 deletions numba/core/types/functions.py
Expand Up @@ -38,7 +38,7 @@ def _wrapper(tmp, indent=0):
return textwrap.indent(tmp, ' ' * indent, lambda line: True)

_overload_template = ("- Of which {nduplicates} did not match due to:\n"
"Overload in function '{function}': File: {file}: "
"{kind} {inof} function '{function}': File: {file}: "
"Line {line}.\n With argument(s): '({args})':")

_err_reasons = {}
Expand Down Expand Up @@ -87,34 +87,21 @@ def add_error(self, calltemplate, matched, error, literal):
self._failures[key].append(_FAILURE(calltemplate, matched, error,
literal))

def _get_source_info(self, source_fn):
source_line = "<N/A>"
if 'builtin_function' in str(type(source_fn)):
source_file = "<built-in>"
else:
try:
source_file = path.abspath(inspect.getsourcefile(source_fn))
source_line = inspect.getsourcelines(source_fn)[1]
here = path.abspath(__file__)
common = path.commonpath([here, source_file])
source_file = source_file.replace(common, 'numba')
except:
source_file = "Unknown"
return source_file, source_line

def format(self):
"""Return a formatted error message from all the gathered errors.
"""
indent = ' ' * self._scale
argstr = argsnkwargs_to_str(self._args, self._kwargs)
ncandidates = sum([len(x) for x in self._failures.values()])


# sort out a display name for the function
tykey = self._function_type.typing_key
# most things have __name__
fname = getattr(tykey, '__name__', None)
is_external_fn_ptr = isinstance(self._function_type,
ExternalFunctionPointer)

if fname is None:
if is_external_fn_ptr:
fname = "ExternalFunctionPointer"
Expand All @@ -129,55 +116,87 @@ def format(self):
nolitkwargs = {k: unliteral(v) for k, v in self._kwargs.items()}
nolitargstr = argsnkwargs_to_str(nolitargs, nolitkwargs)

key = self._function_type.key[0]
fn_name = getattr(key, '__name__', str(key))

# depth could potentially get massive, so limit it.
ldepth = min(max(self._depth, 0), self._max_depth)

def template_info(tp):
src_info = tp.get_template_info()
unknown = "unknown"
source_name = src_info.get('name', unknown)
source_file = src_info.get('filename', unknown)
source_lines = src_info.get('lines', unknown)
source_kind = src_info.get('kind', 'Unknown template')
return source_name, source_file, source_lines, source_kind

for i, (k, err_list) in enumerate(self._failures.items()):
err = err_list[0]
nduplicates = len(err_list)
template, error = err.template, err.error
source_fn = template.key
if isinstance(source_fn, numba.core.extending._Intrinsic):
source_fn = template.key._defn
elif (is_external_fn_ptr and
isinstance(source_fn, numba.core.typing.templates.Signature)):
source_fn = template.__class__
elif hasattr(template, '_overload_func'):
source_fn = template._overload_func
source_file, source_line = self._get_source_info(source_fn)
ifo = template_info(template)
source_name, source_file, source_lines, source_kind = ifo
largstr = argstr if err.literal else nolitargstr
msgbuf.append(_termcolor.errmsg(
_wrapper(_overload_template.format(nduplicates=nduplicates,
function=source_fn.__name__,
file=source_file,
line=source_line,
args=largstr),
ldepth + 1)))

if isinstance(error, BaseException):
reason = indent + self.format_error(error)
errstr = _err_reasons['specific_error'].format(reason)

if err.error == "No match.":
err_dict = defaultdict(set)
for errs in err_list:
err_dict[errs.template].add(errs.literal)
# if there's just one template, and it's erroring on
# literal/nonliteral be specific
if len(err_dict) == 1:
template = [_ for _ in err_dict.keys()][0]
source_name, source_file, source_lines, source_kind = \
template_info(template)
source_lines = source_lines[0]
else:
source_file = "<numerous>"
source_lines = "N/A"

msgbuf.append(_termcolor.errmsg(
_wrapper(_overload_template.format(nduplicates=nduplicates,
kind = source_kind.title(),
function=fname,
inof='of',
file=source_file,
line=source_lines,
args=largstr),
ldepth + 1)))
msgbuf.append(_termcolor.highlight(_wrapper(err.error,
ldepth + 2)))
else:
errstr = error
# if you are a developer, show the back traces
if config.DEVELOPER_MODE:
# There was at least one match in this failure class, but it
# failed for a specific reason try and report this.
msgbuf.append(_termcolor.errmsg(
_wrapper(_overload_template.format(nduplicates=nduplicates,
kind = source_kind.title(),
function=source_name,
inof='in',
file=source_file,
line=source_lines[0],
args=largstr),
ldepth + 1)))

if isinstance(error, BaseException):
# if the error is an actual exception instance, trace it
bt = traceback.format_exception(type(error), error,
error.__traceback__)
reason = indent + self.format_error(error)
errstr = _err_reasons['specific_error'].format(reason)
else:
bt = [""]
bt_as_lines = _bt_as_lines(bt)
nd2indent = '\n{}'.format(2 * indent)
errstr += _termcolor.reset(nd2indent +
nd2indent.join(bt_as_lines))
msgbuf.append(_termcolor.highlight(_wrapper(errstr, ldepth + 2)))
loc = self.get_loc(template, error)
if loc:
msgbuf.append('{}raised from {}'.format(indent, loc))
errstr = error
# if you are a developer, show the back traces
if config.DEVELOPER_MODE:
if isinstance(error, BaseException):
# if the error is an actual exception instance, trace it
bt = traceback.format_exception(type(error), error,
error.__traceback__)
else:
bt = [""]
bt_as_lines = _bt_as_lines(bt)
nd2indent = '\n{}'.format(2 * indent)
errstr += _termcolor.reset(nd2indent +
nd2indent.join(bt_as_lines))
msgbuf.append(_termcolor.highlight(_wrapper(errstr,
ldepth + 2)))
loc = self.get_loc(template, error)
if loc:
msgbuf.append('{}raised from {}'.format(indent, loc))

# the commented bit rewraps each block, may not be helpful?!
return _wrapper('\n'.join(msgbuf) + '\n') # , self._scale * ldepth)
Expand Down
107 changes: 105 additions & 2 deletions numba/core/typing/templates.py
Expand Up @@ -2,6 +2,7 @@
Define typing templates
"""

from abc import ABC, abstractmethod
import functools
import sys
import inspect
Expand Down Expand Up @@ -246,7 +247,7 @@ def fold_arguments(pysig, args, kws, normal_handler, default_handler,
return args


class FunctionTemplate(object):
class FunctionTemplate(ABC):
# Set to true to disable unsafe cast.
# subclass overide-able
unsafe_casting = True
Expand Down Expand Up @@ -277,6 +278,24 @@ def get_impl_key(self, sig):
key = key.im_func
return key

@abstractmethod
def get_template_info(self):
"""
Returns a dictionary with information specific to the template that will
govern how error messages are displayed to users. The dictionary must
be of the form:
info = {
'kind': "unknown", # str: The kind of template, e.g. "Overload"
'name': "unknown", # str: The name of the source function
'sig': "unknown", # str: The signature(s) of the source function
'filename': "unknown", # str: The filename of the source function
'lines': ("start", "end"), # tuple(int, int): The start and
end line of the source function.
'docstring': "unknown" # str: The docstring of the source function
}
"""
pass


class AbstractTemplate(FunctionTemplate):
"""
Expand Down Expand Up @@ -310,6 +329,22 @@ def unpack_opt(x):

return sig

def get_template_info(self):
impl = getattr(self, "generic")
basepath = os.path.dirname(os.path.dirname(numba.__file__))
code, firstlineno = inspect.getsourcelines(impl)
path = inspect.getsourcefile(impl)
sig = str(utils.pysignature(impl))
info = {
'kind': "overload",
'name': getattr(impl, '__qualname__', impl.__name__),
'sig': sig,
'filename': os.path.relpath(path, start=basepath),
'lines': (firstlineno, firstlineno + len(code) - 1),
'docstring': impl.__doc__
}
return info


class CallableTemplate(FunctionTemplate):
"""
Expand Down Expand Up @@ -369,6 +404,23 @@ def unpack_opt(x):
cases = [sig]
return self._select(cases, bound.args, bound.kwargs)

def get_template_info(self):
impl = getattr(self, "generic")
basepath = os.path.dirname(os.path.dirname(numba.__file__))
code, firstlineno = inspect.getsourcelines(impl)
path = inspect.getsourcefile(impl)
sig = str(utils.pysignature(impl))
info = {
'kind': "overload",
'name': getattr(self.key, '__name__',
getattr(impl, '__qualname__', impl.__name__),),
'sig': sig,
'filename': os.path.relpath(path, start=basepath),
'lines': (firstlineno, firstlineno + len(code) - 1),
'docstring': impl.__doc__
}
return info


class ConcreteTemplate(FunctionTemplate):
"""
Expand All @@ -380,6 +432,25 @@ def apply(self, args, kws):
cases = getattr(self, 'cases')
return self._select(cases, args, kws)

def get_template_info(self):
import operator
name = getattr(self.key, '__name__', "unknown")
op_func = getattr(operator, name, None)

kind = "Type restricted function"
if op_func is not None:
if self.key is op_func:
kind = "operator overload"
info = {
'kind': kind,
'name': name,
'sig': "unknown",
'filename': "unknown",
'lines': ("unknown", "unknown"),
'docstring': "unknown"
}
return info


class _EmptyImplementationEntry(InternalError):
def __init__(self, reason):
Expand Down Expand Up @@ -675,6 +746,22 @@ def get_source_info(cls):
}
return info

def get_template_info(self):
basepath = os.path.dirname(os.path.dirname(numba.__file__))
impl = self._overload_func
code, firstlineno = inspect.getsourcelines(impl)
path = inspect.getsourcefile(impl)
sig = str(utils.pysignature(impl))
info = {
'kind': "overload",
'name': getattr(impl, '__qualname__', impl.__name__),
'sig': sig,
'filename': os.path.relpath(path, start=basepath),
'lines': (firstlineno, firstlineno + len(code) - 1),
'docstring': impl.__doc__
}
return info


def make_overload_template(func, overload_func, jit_options, strict,
inline):
Expand Down Expand Up @@ -728,6 +815,22 @@ def get_impl_key(self, sig):
"""
return self._overload_cache[sig.args]

def get_template_info(self):
basepath = os.path.dirname(os.path.dirname(numba.__file__))
impl = self._definition_func
code, firstlineno = inspect.getsourcelines(impl)
path = inspect.getsourcefile(impl)
sig = str(utils.pysignature(impl))
info = {
'kind': "intrinsic",
'name': getattr(impl, '__qualname__', impl.__name__),
'sig': sig,
'filename': os.path.relpath(path, start=basepath),
'lines': (firstlineno, firstlineno + len(code) - 1),
'docstring': impl.__doc__
}
return info


def make_intrinsic_template(handle, defn, name):
"""
Expand Down Expand Up @@ -885,7 +988,7 @@ def make_overload_attribute_template(typ, attr, overload_func, inline,
*overload_func*.
"""
assert isinstance(typ, types.Type) or issubclass(typ, types.Type)
name = "OverloadTemplate_%s_%s" % (typ, attr)
name = "OverloadAttributeTemplate_%s_%s" % (typ, attr)
# Note the implementation cache is subclass-specific
dct = dict(key=typ, _attr=attr, _impl_cache={},
_inline=staticmethod(InlineOptions(inline)),
Expand Down
20 changes: 19 additions & 1 deletion numba/cuda/compiler.py
@@ -1,4 +1,5 @@
import ctypes
import inspect
import os
import sys

Expand All @@ -8,7 +9,7 @@
from numba.core import (types, typing, utils, funcdesc, serialize, config,
compiler, sigutils)
from numba.core.compiler_lock import global_compiler_lock

import numba
from .cudadrv.devices import get_context
from .cudadrv import nvvm, driver
from .errors import normalize_kernel_dimensions
Expand Down Expand Up @@ -137,6 +138,8 @@ def __init__(self, pyfunc, debug, inline):
self.debug = debug
self.inline = inline
self._compileinfos = {}
name = getattr(pyfunc, '__name__', 'unknown')
self.__name__ = f"{name} <CUDA device function>".format(name)

def __reduce__(self):
glbls = serialize._get_function_globals_for_reduction(self.py_func)
Expand Down Expand Up @@ -231,6 +234,21 @@ def generic(self, args, kws):
assert not kws
return dft.compile(args).signature

def get_template_info(cls):
basepath = os.path.dirname(os.path.dirname(numba.__file__))
code, firstlineno = inspect.getsourcelines(pyfunc)
path = inspect.getsourcefile(pyfunc)
sig = str(utils.pysignature(pyfunc))
info = {
'kind': "overload",
'name': getattr(cls.key, '__name__', "unknown"),
'sig': sig,
'filename': os.path.relpath(path, start=basepath),
'lines': (firstlineno, firstlineno + len(code) - 1),
'docstring': pyfunc.__doc__
}
return info

typingctx = CUDATargetDesc.typingctx
typingctx.insert_user_function(dft, device_function_template)
return dft
Expand Down

0 comments on commit b945a8a

Please sign in to comment.