classification
Title: unittest.assertRaisesRegex is broken in Python 3.11 and leading to crashing if tested regex does not match name.
Type: crash Stage: resolved
Components: Library (Lib) Versions: Python 3.11, Python 3.10
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Dennis Sweeney, lukasz.langa, pablogsal, xtreak, xxm
Priority: normal Keywords: patch

Created on 2021-11-17 02:50 by xxm, last changed 2021-11-18 00:28 by lukasz.langa. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 29590 merged Dennis Sweeney, 2021-11-17 07:58
PR 29602 merged lukasz.langa, 2021-11-17 23:04
Messages (8)
msg406445 - (view) Author: Xinmeng Xia (xxm) Date: 2021-11-17 02:50
In Python 3.11, unittest.assertRaisesRegex is broken and leading to crashing if tested regex does not match name. See the following example:

test.py
=========================================
import unittest

class uTest(unittest.TestCase):
	pass

uTest = uTest()

with uTest.assertRaisesRegex(Exception, 'aaa'):
     aab
=========================================


Output in Python3.9.2, 3.10:
--------------------------------------
NameError: name 'aab' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/xxm/Desktop/test.py", line 29, in <module>
    aab
  File "/usr/local/python310/lib/python3.10/unittest/case.py", line 239, in __exit__
    self._raiseFailure('"{}" does not match "{}"'.format(
  File "/usr/local/python310/lib/python3.10/unittest/case.py", line 163, in _raiseFailure
    raise self.test_case.failureException(msg)
AssertionError: "aaa" does not match "name 'aab' is not defined
--------------------------------------------------

Actual output in Python3.11.0a1,Python3.11.0a2:
Segmentation fault (core dumped)

System: Ubuntu 16.04
msg406450 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2021-11-17 04:29
Running it in debug build mode

./python -X dev ../bpo45826.py                                
python: Python/suggestions.c:215: offer_suggestions_for_name_error: Assertion `frame != NULL' failed.
Fatal Python error: Aborted

Current thread 0x00007f4c717f3280 (most recent call first):
  <no Python frame>
[1]    15180 abort (core dumped)  ./python -X dev ../bpo45826.py
msg406451 - (view) Author: Dennis Sweeney (Dennis Sweeney) * (Python triager) Date: 2021-11-17 04:37
I got a segfault in a similar location:

static PyObject *
offer_suggestions_for_name_error(PyNameErrorObject *exc)
{
    PyObject *name = exc->name; // borrowed reference
    PyTracebackObject *traceback = (PyTracebackObject *) exc->traceback; // borrowed reference
    // Abort if we don't have a variable name or we have an invalid one
    // or if we don't have a traceback to work with
    if (name == NULL || traceback == NULL || !PyUnicode_CheckExact(name)) {
        return NULL;
    }

    // Move to the traceback of the exception
    while (traceback->tb_next != NULL) {  <<<<<<<<<<<<<<< segfault: traceback is junk (but not null) pointer
        traceback = traceback->tb_next;
    }
...


Adding ```assert(Py_TYPE(exc) == PyExc_NameError);``` fails, so somehow something is getting cast to ```PyNameErrorObject *``` when it shouldn't be.

Here is some debugging code I used that also causes the crash:


----------------------------------------------
from unittest import TestCase
from unittest.case import _AssertRaisesContext
import sys
import traceback

manager = _AssertRaisesContext(Exception, TestCase(), 'aaa')

# inline this:
# with manager:
#      aab

try:
    aab
except:

    # inline __exit__
    exc_type, exc_value, tb = sys.exc_info()
    traceback.clear_frames(tb)
    manager.exception = exc_value.with_traceback(None)
    output = '"{}" does not match "{}"'.format(
                     manager.expected_regex.pattern, str(exc_value))

    # inline manager._raiseFailure(output)
    msg = manager.test_case._formatMessage(manager.msg, output)
    print("A:", f"{msg=!r}")
    e = manager.test_case.failureException(msg)
    print("B:", f"{e=!r}")
    raise e

# Output:
# A: msg='"aaa" does not match "name \'aab\' is not defined"'
# B: e=AssertionError('"aaa" does not match "name \'aab\' is not defined"')
-----------------------------------------------
msg406454 - (view) Author: Dennis Sweeney (Dennis Sweeney) * (Python triager) Date: 2021-11-17 04:53
Here's shorter reproducer not involving unittest:


import sys

try:
    aab
except:
    exc_type, exc_value, tb = sys.exc_info()
    exc_value.with_traceback(None)
    raise ZeroDivisionError()
msg406456 - (view) Author: Dennis Sweeney (Dennis Sweeney) * (Python triager) Date: 2021-11-17 05:58
Even shorter reproducer:

-----------------------------
try:
    aab
except BaseException as E:
    E.with_traceback(None)
    raise ZeroDivisionError()
-----------------------------


Bisection points to the initial implementation of suggestions.c:

5bf8bf2267cd109970b2d946d43b2e9f71379ba2 is the first bad commit
commit 5bf8bf2267cd109970b2d946d43b2e9f71379ba2
Author: Pablo Galindo <Pablogsal@gmail.com>
Date:   Wed Apr 14 15:10:33 2021 +0100

    bpo-38530: Offer suggestions on NameError (GH-25397)

    When printing NameError raised by the interpreter, PyErr_Display
    will offer suggestions of simmilar variable names in the function that the exception
    was raised from:

        >>> schwarzschild_black_hole = None
        >>> schwarschild_black_hole
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        NameError: name 'schwarschild_black_hole' is not defined. Did you mean: schwarzschild_black_hole?
msg406506 - (view) Author: Łukasz Langa (lukasz.langa) * (Python committer) Date: 2021-11-17 23:04
New changeset 5d90c467c02ffefdb13c1abc83a171db1a99ffad by Dennis Sweeney in branch 'main':
bpo-45826: Fix a crash in suggestions.c by checking for `traceback is None` (GH-29590)
https://github.com/python/cpython/commit/5d90c467c02ffefdb13c1abc83a171db1a99ffad
msg406513 - (view) Author: Łukasz Langa (lukasz.langa) * (Python committer) Date: 2021-11-18 00:28
New changeset 8eabe60108b536b942c791b5d3dc3c3020497aac by Łukasz Langa in branch '3.10':
[3.10] bpo-45826: Fix a crash in suggestions.c by checking for `traceback is None` (GH-29590) (GH-29602)
https://github.com/python/cpython/commit/8eabe60108b536b942c791b5d3dc3c3020497aac
msg406514 - (view) Author: Łukasz Langa (lukasz.langa) * (Python committer) Date: 2021-11-18 00:28
Thanks, Dennis! ✨ 🍰 ✨
History
Date User Action Args
2021-11-18 00:28:35lukasz.langasetstatus: open -> closed
versions: + Python 3.10
messages: + msg406514

resolution: fixed
stage: patch review -> resolved
2021-11-18 00:28:08lukasz.langasetmessages: + msg406513
2021-11-17 23:04:49lukasz.langasetpull_requests: + pull_request27845
2021-11-17 23:04:26lukasz.langasetnosy: + lukasz.langa
messages: + msg406506
2021-11-17 07:58:41Dennis Sweeneysetkeywords: + patch
stage: patch review
pull_requests: + pull_request27833
2021-11-17 05:58:06Dennis Sweeneysetmessages: + msg406456
2021-11-17 04:53:55Dennis Sweeneysetmessages: + msg406454
2021-11-17 04:37:46Dennis Sweeneysetnosy: + Dennis Sweeney
messages: + msg406451
2021-11-17 04:29:47xtreaksetnosy: + pablogsal, xtreak
messages: + msg406450
2021-11-17 02:50:58xxmcreate