Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bad attr access on certain typing templates breaking exceptions. #5912

Merged
merged 2 commits into from Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -37,7 +37,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 @@ -86,34 +86,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 @@ -128,55 +115,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 @@ -667,6 +738,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 @@ -720,6 +807,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 @@ -877,7 +980,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