GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `PyErr_SetObject()` behavior is strange and not as documented. · Issue #101578 · python/cpython

PyErr_SetObject() behavior is strange and not as documented. #101578

New issue
Copy link
New issue
Copy link
Closed
#101607
Closed
PyErr_SetObject() behavior is strange and not as documented.#101578
#101607
Copy link
Labels
3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixes3.7 (EOL)end of lifeend of life3.8 (EOL)end of lifeend of life3.9 (EOL)end of lifeend of lifetopic-C-APItype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@markshannon
markshannon
opened on Feb 5, 2023
Issue body actions

Briefly:
PyErr_SetObject(exc_type, exc_val) does not create a new exception iff isinstance(exc_val, BaseException), but uses exc_val instead.

Callers of PyErr_SetObject() need various workarounds to handle this.

The long version:

Internally CPython handles exceptions as a triple (type, value, traceback), but the language treats exceptions as a single value.

This a legacy of the olden days before proper exceptions.
To handle adding proper exceptions to Python, various error handling functions, specifically _PyErr_SetObject still treat exceptions as triples, with the convention that if the value is an exception, then the exception is already normalized.

One other oddity is that if exc_val is a tuple, it is treated as the * arguments to exc_type when calling it. So, if isinstance(exc_val, BaseException) the desired behavior can be achieved by wrapping exc_val in a one-tuple.

As a consequence, both _PyErr_SetKeyError and _PyGen_SetStopIterationValue are a lot more complex than they should be to workaround this behavior.

We could make PyErr_SetObject act as documented, but that is likely to break C extensions, given how old this behavior is, and that it is relied on throughout CPython.

Code that does the following is common:

    exc = new_foo_exception();
    PyErr_SetObject(&PyFooException_Type, exc);

We could just document the current behavior, but the current behavior is strange.
What I suggest is this:

  • Create a new API function, PyErr_SetException(exc)` that takes a single exception object.
  • Document PyErr_SetObject() accurately
  • Deprecate the old function

This is an old bug going back to the 2 series.

Linked PRs

  • GH-101578: Normalize the current exception #101607
  • gh-101578: Amend PyErr_{Set,Get}RaisedException docs #101962
  • gh-101578: Amend exception docs #102057
  • gh-101578: Fixup NEWS and add What's New entry for new exception APIs #102157
  • gh-101578: mention in what's new in 3.12 that exceptions are now normalized before stored #102702
  • gh-101578: [doc] mention that PyErr_GetRaisedException returns NULL when the error indicator is not set #113369
  • [3.12] gh-101578: [doc] mention that PyErr_GetRaisedException returns NULL when the error indicator is not set (GH-113369) #113606

Also relevant:

  • gh-102493: fix normalization in PyErr_SetObject #102502

Activity

Next
  1. markshannon
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    3.11only security fixesonly security fixes
    3.10 (EOL)end of lifeend of life
    3.9 (EOL)end of lifeend of life
    3.8 (EOL)end of lifeend of life
    3.7 (EOL)end of lifeend of life
    topic-C-API
    3.12only security fixesonly security fixes
    on Feb 5, 2023
  2. markshannon commented on Feb 5, 2023

    @markshannon
    markshannon
    on Feb 5, 2023
    MemberAuthor
    More actions

    The only case I can find of strange behavior leaking out is this:

    import sys
    
    def wrap_in_sys_exit(ex):
        try:
            try:
                raise ex(-1)
            except ex as err:
                sys.exit(err)
        except BaseException as err:
            return err
    
    print(repr(wrap_in_sys_exit(ValueError)))
    print(repr(wrap_in_sys_exit(SystemExit)))

    Which prints

    SystemExit(ValueError(-1))
    SystemExit(-1)
    

    instead of the expected

    SystemExit(ValueError(-1))
    SystemExit(SystemExit(-1))
    
  3. rhettinger commented on Feb 5, 2023

    @rhettinger
    rhettinger
    on Feb 5, 2023
    Contributor
    More actions

    This API goes by to 1997. It is remarkable how simple and clear the code used to be:

    void
    PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
    {
    	PyThreadState *tstate = PyThreadState_GET();
    	PyObject *oldtype, *oldvalue, *oldtraceback;
    
    	if (traceback != NULL && !PyTraceBack_Check(traceback)) {
    		/* XXX Should never happen -- fatal error instead? */
    		Py_DECREF(traceback);
    		traceback = NULL;
    	}
    
    	/* Save these in locals to safeguard against recursive
    	   invocation through Py_XDECREF */
    	oldtype = tstate->curexc_type;
    	oldvalue = tstate->curexc_value;
    	oldtraceback = tstate->curexc_traceback;
    
    	tstate->curexc_type = type;
    	tstate->curexc_value = value;
    	tstate->curexc_traceback = traceback;
    
    	Py_XDECREF(oldtype);
    	Py_XDECREF(oldvalue);
    	Py_XDECREF(oldtraceback);
    }
    
    void
    PyErr_SetObject(PyObject *exception, PyObject *value)
    {
    	Py_XINCREF(exception);
    	Py_XINCREF(value);
    	PyErr_Restore(exception, value, (PyObject *)NULL);
    }
    Reacted by Giampaolo Rodola
  4. markshannon commented on Feb 6, 2023

    @markshannon
    markshannon
    on Feb 6, 2023
    MemberAuthor
    More actions

    The code may have been simple, but it can leave the VM in a precarious state if the value is not a legal argument for calling the type.

  5. markshannon
    closed this as completedin #101607on Feb 8, 2023
  6. markshannon
    added a commit that references this issue on Feb 8, 2023

    GH-101578: Normalize the current exception (GH-101607)

    feec49c
  7. carljm
    added a commit that references this issue on Feb 9, 2023

    Merge branch 'main' into inlinecomp2

    be3becc
  8. erlend-aasland commented on Feb 14, 2023

    @erlend-aasland
    erlend-aasland
    on Feb 14, 2023
    Contributor
    More actions

    The new C APIs PyErr_GetRaisedException and PyErr_SetRaisedException are IMO problematic:

    • the former returns a borrowed reference
    • the latter steals a reference

    This goes against our recommendations for adding new public C APIs.

    I also find the added documentation inadequate. It is not documented that the former function returns a borrowed reference. The documentation for the latter function is not exactly friendly written, and I'm specifically thinking of the last parenthesised sentence:

    exc must be a valid exception. (Violating this rules will cause subtle problems later.) This call consumes a reference to the exc object: you must own a reference to that object before the call and after the call you no longer own that reference. (If you don’t understand this, don’t use this function. I warned you.)

    Suggesting to considering reopening this issue until at least the docs have been improved. IMO, we should also fix the newly added C APIs to be consistent in their reference handling, which means:

    • PyErr_GetRaisedException should return a new reference
    • PyErr_SetRaisedException should not steal a reference
  9. 44 remaining items

  10. iritkatriel
    added a commit that references this issue on Dec 21, 2023

    pythongh-101578: [doc] mention that PyErr_GetRaisedException returns …

    c9112ea
  11. iritkatriel
    added a commit that references this issue on Dec 31, 2023

    gh-101578: [doc] mention that PyErr_GetRaisedException returns NULL w…

    2849cbb
  12. miss-islington
    added a commit that references this issue on Dec 31, 2023

    pythongh-101578: [doc] mention that PyErr_GetRaisedException returns …

    facf8ed
  13. iritkatriel
    added a commit that references this issue on Dec 31, 2023

    [3.12] gh-101578: [doc] mention that PyErr_GetRaisedException returns…

    f3d3663
  14. kulikjak
    added a commit that references this issue on Jan 22, 2024

    pythongh-101578: [doc] mention that PyErr_GetRaisedException returns …

    f83eeca
  15. aisk
    added a commit that references this issue on Feb 11, 2024

    pythongh-101578: [doc] mention that PyErr_GetRaisedException returns …

    443af64
  16. markshannon commented on Aug 15, 2024

    @markshannon
    markshannon
    on Aug 15, 2024
    MemberAuthor
    More actions

    I think this is all done. Thanks @iritkatriel and @erlend-aasland

  17. markshannon
    closed this as completedon Aug 15, 2024
  18. python-sidebar
    added 2 commits that reference this issue on Sep 1, 2024

    pythongh-101578: Amend exception docs (python#102057)

    e9567d5

    pythongh-101578: Amend exception docs (python#102057)

    e412f67
  19. Glyphack
    added a commit that references this issue on Sep 2, 2024

    pythongh-101578: [doc] mention that PyErr_GetRaisedException returns …

    c6911e7
  20. JelleZijlstra
    added 2 commits that reference this issue on Sep 10, 2024

    pythongh-101578: Amend exception docs (python#102057)

    80458f6

    pythongh-101578: Fixup NEWS and add What's New entry for new exceptio…

    d9e8056
  21. Xmader
    added a commit that references this issue on Sep 23, 2024

    refactor: move `_PyErr_SetKeyError` shim to pyshim.hh

    a1cdfb3
  22. NexediGitlab
    added a commit that references this issue on Mar 18, 2026

    bigfile/py: Starting from py3.12 current exception state is kept only…

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixes3.7 (EOL)end of lifeend of life3.8 (EOL)end of lifeend of life3.9 (EOL)end of lifeend of lifetopic-C-APItype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.