CTypes, raising python-style exception from wrapper

0

In patching the __rshift__ operator of primitive python types with a callable, the patching utilises a wrapper:

def _patch_rshift(py_class, func):
    assert isinstance(func, FunctionType)

    py_type_name = 'tp_as_number'
    py_type_method = 'nb_rshift'

    py_obj = PyTypeObject.from_address(id(py_class))
    type_ptr = getattr(py_obj, py_type_name)

    if not type_ptr:
        tp_as_obj = PyNumberMethods()
        FUNC_INDIRECTION_REFS[(py_class, '__rshift__')] = tp_as_obj
        tp_as_new_ptr = ctypes.cast(ctypes.addressof(tp_as_obj),
                                    ctypes.POINTER(PyNumberMethods))
        setattr(py_obj, py_type_name, tp_as_new_ptr)

    type_head = type_ptr[0]
    c_func_t = binary_func_p

    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except BaseException as be:
            # Need to raise a python style error here:
            wrapper.exc_info = sys.exc_info()
            return False

    c_func = c_func_t(wrapper)
    C_FUNC_CALLBACK_REFS[(py_class, '__rshift__')] = c_func

    setattr(type_head, py_type_method, c_func)

The challenge is now to, once an Exception is caught inside wrapper to raise an exception here just as any normal python exception.

Raising like:

    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except BaseException as be:
            raise

or not catching at all:

    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

Yields:

Windows fatal exception: access violation
Process finished with exit code -1073741819 (0xC0000005)

The desired behavior is to simply re-raise the caught exception, but with a python-style output, if possible.

Related answers rely on the platform being Windows-centered to achieve the desired outcome, which is not suitable, do not preserve the original exception, or do not achieve the desired python exception-style behaviour:

Get error message from ctypes windll

Ctypes catching exception

UPDATE:

After some more digging, it would appear that raising anywhere in this method triggers a segfault. None the wiser on how to solve it.

python
ctypes
asked on Stack Overflow Sep 12, 2019 by Zander Fick • edited Sep 12, 2019 by Zander Fick

1 Answer

0

Not a solution, but a workaround:

Manually collecting the error information (Using traceback and inspect), printing to sys.stderr, and then returning a pointer to a CTypes error singleton (As done in forbiddenfruit) prevents the segfault from occuring.

The output would then be:

  • Custom error code
  • Python error code as one would expect from the un-patched primitive

(For brevity, some of the methods used here are not included as they do not add any value to the solution, I opted for a pytest style error format.)

    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except BaseException as be:
            # Traceback will not help use here, assemble a custom error message:
            from custom_inspect_utils import get_calling_expression_recursively
            import sys
            calling_file, calling_expression = get_calling_expression_recursively()

            indentation = " "*(len(calling_expression)-len(calling_expression.lstrip()))

            print(f"\n{calling_file}\n>{calling_expression}E{indentation}{type(be).__name__}: {be}", file=sys.stderr)
            print(f"\n\tAs this package patches primitive types, the python RTE also raised:", file=sys.stderr)
            return ErrorSingleton

e.g. After patching list to implement __rshift__, the expression [1,2,3] >> wrong_target where one expects a CustomException will now first output

source_file.py:330 (method_name)
>    [1,2,3] >> wrong_target
E    CustomException: Message.

followed by a TypeError:

TypeError: unsupported operand type(s) for >>: 'list' and 'WrongType'
answered on Stack Overflow Sep 12, 2019 by Zander Fick • edited Sep 13, 2019 by Zander Fick

User contributions licensed under CC BY-SA 3.0