Skip to content

Conversation

@DavidEGrayson
Copy link
Contributor

@DavidEGrayson DavidEGrayson commented Apr 12, 2023

This new function makes it easier to provide custom formatting of exception stack traces, for example to make them fit on a tiny 16x8-character display. (Without this, I think you'd have to call sys.print_exception, somehow get the output as a string, and then scrape information from that string.)

I'm thinking of this as an experimental function that exposes internal details of MicroPython and therefore might change in the future. That's why it has the underscore in the name.

Here is an example of its output:

['test.py', 4, 'foo', 'test.py', 7, '<module>']

The regular output from sys.print_exception for the same exception is:

Traceback (most recent call last):
  File "test.py", line 7, in <module>
  File "test.py", line 4, in foo
ZeroDivisionError: divide by zero

Both outputs were generated by the following script:

import sys

def foo():
  1/0

try:
  foo()
except Exception as e:
  sys.print_exception(e)
  print(sys._exc_traceback(e))

Is this the right approach for getting info about exception stack traces? I'd be happy to change the name, change the output format, add tests, or add documentation.

@github-actions
Copy link

github-actions bot commented Apr 12, 2023

Code size report:

Reference:  tools/mpy_ld.py: Write architecture flags to output natmod if needed. [b87d73f]
Comparison: py/modsys.c: Add sys._exc_traceback. [merge of 2775717]
  mpy-cross:    +0 +0.000% 
   bare-arm:    +0 +0.000% 
minimal x86:  +439 +0.234% [incl +64(data)]
   unix x64:  +416 +0.049% standard[incl +32(data)]
      stm32:  +156 +0.040% PYBV10
      esp32:  +168 +0.010% ESP32_GENERIC[incl +48(data)]
     mimxrt:  +168 +0.045% TEENSY40
        rp2:  +184 +0.020% RPI_PICO_W
       samd:  +172 +0.063% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:  +181 +0.040% VIRT_RV32

@codecov-commenter
Copy link

codecov-commenter commented Apr 12, 2023

Codecov Report

❌ Patch coverage is 0% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.33%. Comparing base (b87d73f) to head (2775717).

Files with missing lines Patch % Lines
py/modsys.c 0.00% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11244      +/-   ##
==========================================
- Coverage   98.38%   98.33%   -0.05%     
==========================================
  Files         171      171              
  Lines       22298    22309      +11     
==========================================
  Hits        21937    21937              
- Misses        361      372      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dpgeorge dpgeorge added the py-core Relates to py/ directory in source label Apr 12, 2023
@dpgeorge
Copy link
Member

First thing to ask is how would one do this in CPython?

Then we need to consider if it's worth the cost (code size) and whether it should be guarded by a config macro. I would say yes it should be, but then at what level would it be enabled, eg MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES.

@DavidEGrayson
Copy link
Contributor Author

DavidEGrayson commented Apr 14, 2023

You can get this info in CPython is like this:

import traceback

def foo():
    1/0

try:
    foo()
except Exception as e:
    tb = e.__traceback__
    while tb is not None:
        print("====")
        print("  filename: " + str(tb.tb_frame.f_code.co_filename))
        print("  line: " + str(tb.tb_lineno))
        print("  name: " + str(tb.tb_frame.f_code.co_name))
        tb = tb.tb_next

This involves at least three different classes: traceback, frame, and code. The latter two contain quite a few things:

dir(tb): ['tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next']
dir(frame): ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'f_back', 'f_builtins', 'f_code', 'f_globals', 'f_lasti', 'f_lineno', 'f_locals', 'f_trace', 'f_trace_lines', 'f_trace_opcodes']
dir(code): ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lines', 'co_linetable', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']

The Python standard library also comes with a traceback module that introduces more classes. MicroPython implements a minimal traceback module.

CPython does not have sys.print_exception like MicroPython does.

@DavidEGrayson
Copy link
Contributor Author

I changed it so that this function is only enabled by default if MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES is 1. (I'm glad to see this provides it for the RP2 boards.) I also added documentation.

@DavidEGrayson DavidEGrayson force-pushed the pr_traceback branch 3 times, most recently from 4b5c6c3 to d872e6e Compare April 21, 2023 01:14
@projectgus
Copy link
Contributor

This is an automated heads-up that we've just merged a Pull Request
that removes the STATIC macro from MicroPython's C API.

See #13763

A search suggests this PR might apply the STATIC macro to some C code. If it
does, then next time you rebase the PR (or merge from master) then you should
please replace all the STATIC keywords with static.

Although this is an automated message, feel free to @-reply to me directly if
you have any questions about this.

This makes it easier to provide custom formatting of exception
stack traces, e.g. to make them fit on a tiny display.

This is an experimental function that exposes internal details
of MicroPython and it might change in the future.

Signed-off-by: David (Pololu) <dev-david@pololu.com>
@dpgeorge
Copy link
Member

Thanks for keeping this PR alive. I can see how it would be useful.

To reduce code size I suggest implementing the functionality as an attribute of the exception type, rather than as a new function. So it would go in mp_obj_exception_attr(). To keep code size further down, it could reuse the existing __traceback__ attribute name. So the example from above would be:

import sys

def foo():
  1/0

try:
  foo()
except Exception as e:
  sys.print_exception(e)
  print(e.__traceback__)

Now, that's not fully compatible with CPython, but it's a little bit closer than having a separate sys._exc_traceback() function. We can say that if you want compatibility with CPython then you must consider e.__traceback__ as an opaque (or implementation defined) value, and then use the traceback module to convert that opaque value to something usable. Eg traceback.extract_tb(e.__traceback__) would work the same in CPython and MicroPython, even though the underlying value of e.__traceback__ was different.

And then if you don't care about CPython compatibility and you want to efficiently get the traceback data, you can use the value of e.__traceback__ directly (although maybe we should document it as opaque and subject to change).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

py-core Relates to py/ directory in source

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants