Skip to content

Traceback module improvements #7046

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

Merged
merged 6 commits into from
Oct 13, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 79 additions & 56 deletions shared-bindings/traceback/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,45 @@
//| """
//| ...

STATIC void traceback_exception_common(mp_print_t *print, mp_obj_t value, mp_obj_t tb_obj, mp_obj_t limit_obj) {
STATIC void traceback_exception_common(bool is_print_exception, mp_print_t *print, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_exc, ARG_value, ARG_tb, ARG_limit, ARG_file, ARG_chain };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_value, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_tb, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_limit, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_file, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_chain, MP_ARG_BOOL, {.u_bool = true} },
};

mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

mp_obj_t value = args[ARG_value].u_obj;
if (value == MP_OBJ_NULL) {
value = args[ARG_exc].u_obj;
}
mp_obj_t tb_obj = args[ARG_tb].u_obj;
mp_obj_t limit_obj = args[ARG_limit].u_obj;

if (args[ARG_file].u_obj != mp_const_none) {
if (!is_print_exception) {
#if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE
mp_arg_error_terse_mismatch();
#else
mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("unexpected keyword argument '%q'"), MP_QSTR_file);
#endif

}
#if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES
mp_get_stream_raise(args[ARG_file].u_obj, MP_STREAM_OP_WRITE);
print->data = MP_OBJ_TO_PTR(args[ARG_file].u_obj);
print->print_strn = mp_stream_write_adaptor;
#else
mp_raise_NotImplementedError(translate("file write is not available"));
#endif
}

if (!mp_obj_is_exception_instance(value)) {
mp_raise_TypeError(translate("invalid exception"));
}
Expand All @@ -53,7 +91,9 @@ STATIC void traceback_exception_common(mp_print_t *print, mp_obj_t value, mp_obj
mp_obj_exception_t *exc = mp_obj_exception_get_native(value);
mp_obj_traceback_t *trace_backup = exc->traceback;

if (tb_obj != mp_const_none && print_tb) {
if (tb_obj == MP_OBJ_NULL) {
/* Print the traceback's exception as is */
} else if (tb_obj != mp_const_none && print_tb) {
exc->traceback = mp_arg_validate_type(tb_obj, &mp_type_traceback, MP_QSTR_tb);
} else {
exc->traceback = (mp_obj_traceback_t *)&mp_const_empty_traceback_obj;
Expand All @@ -64,14 +104,24 @@ STATIC void traceback_exception_common(mp_print_t *print, mp_obj_t value, mp_obj
}

//| def format_exception(
//| etype: Type[BaseException],
//| value: BaseException,
//| tb: TracebackType,
//| exc: BaseException | Type[BaseException],
//| /,
//| value: Optional[BaseException] = None,
//| tb: Optional[TracebackType] = None,
//| limit: Optional[int] = None,
//| chain: Optional[bool] = True,
//| ) -> None:
//| ) -> List[str]:
//| """Format a stack trace and the exception information.
//|
//| If the exception value is passed in ``exc``, then this exception value and its
//| associated traceback are used. This is compatible with CPython 3.10 and newer.
//|
//| If the exception value is passed in ``value``, then any value passed in for
//| ``exc`` is ignored. ``value`` is used as the exception value and the
//| traceback in the ``tb`` argument is used. In this case, if ``tb`` is None,
//| no traceback will be shown. This is compatible with CPython 3.5 and
//| newer.
//|
//| The arguments have the same meaning as the corresponding arguments
//| to print_exception(). The return value is a list of strings, each
//| ending in a newline and some containing internal newlines. When
Expand All @@ -80,54 +130,50 @@ STATIC void traceback_exception_common(mp_print_t *print, mp_obj_t value, mp_obj
//|
//| .. note:: Setting ``chain`` will have no effect as chained exceptions are not yet implemented.
//|
//| :param Type[BaseException] etype: This is ignored and inferred from the type of ``value``.
//| :param BaseException value: The exception. Must be an instance of `BaseException`.
//| :param TracebackType tb: The traceback object. If `None`, the traceback will not be printed.
//| :param exc: The exception. Must be an instance of `BaseException`. Unused if value is specified.
//| :param value: If specified, is used in place of ``exc``.
//| :param TracebackType tb: When value is alsp specified, ``tb`` is used in place of the exception's own traceback. If `None`, the traceback will not be printed.
//| :param int limit: Print up to limit stack trace entries (starting from the caller’s frame) if limit is positive.
//| Otherwise, print the last ``abs(limit)`` entries. If limit is omitted or None, all entries are printed.
//| :param bool chain: If `True` then chained exceptions will be printed (note: not yet implemented).
//|
//| """
//| ...
//|
STATIC mp_obj_t traceback_format_exception(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_etype, ARG_value, ARG_tb, ARG_limit, ARG_chain };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_etype, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_value, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_tb, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_limit, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_chain, MP_ARG_BOOL, {.u_bool = true} },
};

mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

mp_print_t print;
vstr_t vstr;
vstr_init_print(&vstr, 0, &print);
traceback_exception_common(&print, args[ARG_value].u_obj, args[ARG_tb].u_obj, args[ARG_limit].u_obj);
return mp_obj_new_str_from_vstr(&mp_type_str, &vstr);
traceback_exception_common(false, &print, n_args, pos_args, kw_args);
mp_obj_t output = mp_obj_new_str_from_vstr(&mp_type_str, &vstr);
return mp_obj_new_list(1, &output);
}

STATIC MP_DEFINE_CONST_FUN_OBJ_KW(traceback_format_exception_obj, 0, traceback_format_exception);

//| def print_exception(
//| etype: Type[BaseException],
//| value: BaseException,
//| tb: TracebackType,
//| exc: BaseException | Type[BaseException],
//| /,
//| value: Optional[BaseException] = None,
//| tb: Optional[TracebackType] = None,
//| limit: Optional[int] = None,
//| file: Optional[io.FileIO] = None,
//| chain: Optional[bool] = True,
//| ) -> None:
//|
//| """Prints exception information and stack trace entries.
//|
//| If the exception value is passed in ``exc``, then this exception value and its
//| associated traceback are used. This is compatible with CPython 3.10 and newer.
//|
//| If the exception value is passed in ``value``, then any value passed in for
//| ``exc`` is ignored. ``value`` is used as the exception value and the
//| traceback in the ``tb`` argument is used. In this case, if ``tb`` is None,
//| no traceback will be shown. This is compatible with CPython 3.5 and
//| newer.
//|
//| .. note:: Setting ``chain`` will have no effect as chained exceptions are not yet implemented.
//|
//| :param Type[BaseException] etype: This is ignored and inferred from the type of ``value``.
//| :param BaseException value: The exception. Must be an instance of `BaseException`.
//| :param TracebackType tb: The traceback object. If `None`, the traceback will not be printed.
//| :param exc: The exception. Must be an instance of `BaseException`. Unused if value is specified.
//| :param value: If specified, is used in place of ``exc``.
//| :param tb: When value is alsp specified, ``tb`` is used in place of the exception's own traceback. If `None`, the traceback will not be printed.
//| :param int limit: Print up to limit stack trace entries (starting from the caller’s frame) if limit is positive.
//| Otherwise, print the last ``abs(limit)`` entries. If limit is omitted or None, all entries are printed.
//| :param io.FileIO file: If file is omitted or `None`, the output goes to `sys.stderr`; otherwise it should be an open
Expand All @@ -139,31 +185,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(traceback_format_exception_obj, 0, traceback_f
//|

STATIC mp_obj_t traceback_print_exception(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_etype, ARG_value, ARG_tb, ARG_limit, ARG_file, ARG_chain };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_etype, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_value, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_tb, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_limit, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_file, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_chain, MP_ARG_BOOL, {.u_bool = true} },
};

mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

mp_print_t print = mp_plat_print;
if (args[ARG_file].u_obj != mp_const_none) {
#if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES
mp_get_stream_raise(args[ARG_file].u_obj, MP_STREAM_OP_WRITE);
print.data = MP_OBJ_TO_PTR(args[ARG_file].u_obj);
print.print_strn = mp_stream_write_adaptor;
#else
mp_raise_NotImplementedError(translate("file write is not available"));
#endif
}

traceback_exception_common(&print, args[ARG_value].u_obj, args[ARG_tb].u_obj, args[ARG_limit].u_obj);
traceback_exception_common(true, &print, n_args, pos_args, kw_args);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(traceback_print_exception_obj, 0, traceback_print_exception);
Expand Down
4 changes: 2 additions & 2 deletions tests/circuitpython/traceback_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ def fun():
print("\nNo Trace:")
traceback.print_exception(None, exc, None)
print("\nDefault Trace:")
traceback.print_exception(None, exc, exc.__traceback__)
traceback.print_exception(exc)
print("\nLimit=1 Trace:")
traceback.print_exception(None, exc, exc.__traceback__, limit=1)
print("\nLimit=0 Trace:")
traceback.print_exception(None, exc, exc.__traceback__, limit=0)
print("\nLimit=-1 Trace:")
traceback.print_exception(None, exc, exc.__traceback__, limit=-1)
print("".join(traceback.format_exception(None, exc, exc.__traceback__, limit=-1)), end="")


class NonNativeException(Exception):
Expand Down