microsoft / debugpy

An implementation of the Debug Adapter Protocol for Python
https://pypi.org/project/debugpy/
Other
1.84k stars 137 forks source link

Python debugger breaks on caught exception within a decorator and context manager #1155

Open damien135 opened 2 years ago

damien135 commented 2 years ago

Type: Bug

Behaviour

Expected vs. Actual

Expected behavior: If breakpoints are enabled only for Unchaught Exceptions and User Uncaught Exceptions, the debugger should not break in the code sample below.

Actual behavior: The debugger breaks on line 19.

Steps to reproduce:

  1. Open VS code
  2. Run the code sample below (integrated terminal):
    
    from contextlib import contextmanager
    from functools import wraps
    def some_decorator(parameter):
    def generic_wrapper(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            with self.some_context_manager(parameter):
                return func(self, *args, **kwargs)
        return wrapper
    return generic_wrapper

class ExampleClass: @some_decorator('some value') def get_property_value(self): return {}

@contextmanager
def some_context_manager(self, parameter):
    raise Exception("User uncaught exception!")

example = ExampleClass() try: test = example.get_property_value() except Exception as ex: print("Exception successfully caught!")


<!--
**After** creating the issue on GitHub, you can add screenshots and GIFs of what is happening. Consider tools like https://www.cockos.com/licecap/, https://github.com/phw/peek or https://www.screentogif.com/ for GIF creation.
-->

<!-- **NOTE**: Everything below except Python output panel is auto-generated; no editing required. Please do provide Python output panel. -->
# Diagnostic data

-   Python version (& distribution if applicable, e.g. Anaconda): 3.10.6
-   Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): Global
-   Value of the `python.languageServer` setting: Default

<details>

<summary>Output for <code>Python</code> in the <code>Output</code> panel (<code>View</code>→<code>Output</code>, change the drop-down the upper-right of the <code>Output</code> panel to <code>Python</code>)
</summary>

<p>

Exception has occurred: Exception (note: full exception trace is shown but execution is paused at: some_context_manager) User uncaught exception! File "...\TestException.py", line 19, in some_context_manager (Current frame) raise Exception("User uncaught exception!") File "...\TestException.py", line 7, in wrapper with self.some_context_manager(parameter): File "...\TestException.py", line 23, in test = example.get_property_value()

(paths edited for privacy)

</p>
</details>

<details>

<summary>User Settings</summary>

<p>

languageServer: "Jedi"



</p>
</details>

Extension version: 2022.12.1
VS Code version: Code 1.70.1 (6d9b74a70ca9c7733b29f0456fd8195364076dda, 2022-08-10T06:08:33.642Z)
OS version: Windows_NT x64 10.0.19044
Modes:

<details>
<summary>System Info</summary>

|Item|Value|
|---|---|
|CPUs|Intel(R) Core(TM) i7-10610U CPU @ 1.80GHz (8 x 2304)|
|GPU Status|2d_canvas: enabled<br>canvas_oop_rasterization: disabled_off<br>direct_rendering_display_compositor: disabled_off_ok<br>gpu_compositing: enabled<br>multiple_raster_threads: enabled_on<br>opengl: enabled_on<br>rasterization: enabled<br>raw_draw: disabled_off_ok<br>skia_renderer: enabled_on<br>video_decode: enabled<br>video_encode: enabled<br>vulkan: disabled_off<br>webgl: enabled<br>webgl2: enabled|
|Load (avg)|undefined|
|Memory (System)|15.73GB (7.64GB free)|
|Process Argv||
|Screen Reader|no|
|VM|0%|
</details>
<!-- generated by issue reporter -->
damien135 commented 2 years ago

It's been a month and there is no response yet on my report above. Should I provide more information? Am I supposed to add labels to this report? (If so, how?) Please let me know if this is not the right place for this bug report.

brettcannon commented 1 year ago

So sorry for not getting back to you, @damien135 ! It looks like GitHub Actions messed up and didn't run to add our triage-needed label, so this fell through the cracks. I am moving this over to debugpy now to see if they know what's going on.

fabioz commented 1 year ago

Actually, I was checking it here and it should hit as a User uncaught exception (the definition of that is whenever there's an exception from user code which would be raised into the standard library the exception should be shown).

In this case the some_context_manager (user code) would be raised into contextlib (standard library), which is correct.

i.e.: image

So, this isn't really a bug (as it's doing what's intended).

Still, I can see how it can be argued that User uncaught exception should only be handled if the exception when it goes into the standard library code and it'd not cross back to user code anymore.

So, maybe this could be turned into a feature request for that behavior, still, the question is whether we should have yet another breakpoint type for that or if this should be a flag or whether the current behavior is never valid and only that one should be used...

fabioz commented 1 year ago

-- Another possibility would be just making an exception for contexlib...

damien135 commented 1 year ago

Hello Fabioz, thanks for looking into this and for the technical explanation. I'd just like to point out that this was highly confusing to me during debugging. I expected this exception to be caught by the try/except block and therefore be 'silenced'. So I was facing an exception where I didn't want the debugger to break (I just wanted it to be handled by the try/except block), but I wasn't able to "silence" it by any means except by disabling breaks on every other "uncaught" exception as well in the debugger (which I didn't want to do).

Since Python encourages the use of exceptions (EAFP principle), I feel like the pattern I described here isn't too weird to use. I'd argue that what makes most sense here is that the debugger doesn't break. Otherwise, how do I keep working on other parts of this project (where I want to break on uncaught exceptions) without constantly being interrupted by this code snippet (which works as intended and where I just want to trigger particular behavior if an exception is thrown upstream)?