diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 7d80a52c158..896a4ba7a64 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -469,9 +469,12 @@ class _IPAddressBase: return prefixlen @classmethod - def _report_invalid_netmask(cls, netmask_str): + def _report_invalid_netmask(cls, netmask_str, suppress=True): msg = '%r is not a valid netmask' % netmask_str - raise NetmaskValueError(msg) from None + if suppress: + raise NetmaskValueError(msg) from None + else: + raise NetmaskValueError(msg) @classmethod def _prefix_from_prefix_string(cls, prefixlen_str): @@ -489,13 +492,13 @@ class _IPAddressBase: # int allows a leading +/- as well as surrounding whitespace, # so we ensure that isn't the case if not (prefixlen_str.isascii() and prefixlen_str.isdigit()): - cls._report_invalid_netmask(prefixlen_str) + cls._report_invalid_netmask(prefixlen_str, suppress=False) try: prefixlen = int(prefixlen_str) except ValueError: cls._report_invalid_netmask(prefixlen_str) if not (0 <= prefixlen <= cls._max_prefixlen): - cls._report_invalid_netmask(prefixlen_str) + cls._report_invalid_netmask(prefixlen_str, suppress=False) return prefixlen @classmethod @@ -1160,19 +1163,24 @@ class _BaseV4: if isinstance(arg, int): prefixlen = arg if not (0 <= prefixlen <= cls._max_prefixlen): - cls._report_invalid_netmask(prefixlen) + cls._report_invalid_netmask(prefixlen, suppress=False) else: - try: - # Check for a netmask in prefix length form - prefixlen = cls._prefix_from_prefix_string(arg) - except NetmaskValueError: - # Check for a netmask or hostmask in dotted-quad form. - # This may raise NetmaskValueError. - prefixlen = cls._prefix_from_ip_string(arg) + prefixlen = cls._prefix_from_string(arg) netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) cls._netmask_cache[arg] = netmask, prefixlen return cls._netmask_cache[arg] + @classmethod + def _prefix_from_string(cls, arg): + try: + # Check for a netmask in prefix length form + return cls._prefix_from_prefix_string(arg) + except NetmaskValueError: + pass + # Check for a netmask or hostmask in dotted-quad form. + # This may raise NetmaskValueError. + return cls._prefix_from_ip_string(arg) + @classmethod def _ip_int_from_string(cls, ip_str): """Turn the given IP string into an integer for comparison. @@ -1592,7 +1600,7 @@ class _BaseV6: if isinstance(arg, int): prefixlen = arg if not (0 <= prefixlen <= cls._max_prefixlen): - cls._report_invalid_netmask(prefixlen) + cls._report_invalid_netmask(prefixlen, suppress=False) else: prefixlen = cls._prefix_from_prefix_string(arg) netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index f16da116a74..03bce91da8e 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1893,7 +1893,7 @@ class ExceptionContextTestCase(unittest.TestCase): cm = self.assertRaises(configparser.InterpolationMissingOptionError) with cm: parser.get('Paths', 'my_dir') - self.assertIs(cm.exception.__suppress_context__, True) + self.assertIsNone(cm.exception.__context__) def test_get_extended_interpolation(self): parser = configparser.ConfigParser( @@ -1907,7 +1907,7 @@ class ExceptionContextTestCase(unittest.TestCase): cm = self.assertRaises(configparser.InterpolationMissingOptionError) with cm: parser.get('Paths', 'my_dir') - self.assertIs(cm.exception.__suppress_context__, True) + self.assertIsNone(cm.exception.__context__) def test_missing_options(self): parser = configparser.ConfigParser() @@ -1917,19 +1917,19 @@ class ExceptionContextTestCase(unittest.TestCase): """) with self.assertRaises(configparser.NoSectionError) as cm: parser.options('test') - self.assertIs(cm.exception.__suppress_context__, True) + self.assertIsNone(cm.exception.__context__) def test_missing_section(self): config = configparser.ConfigParser() with self.assertRaises(configparser.NoSectionError) as cm: config.set('Section1', 'an_int', '15') - self.assertIs(cm.exception.__suppress_context__, True) + self.assertIsNone(cm.exception.__context__) def test_remove_option(self): config = configparser.ConfigParser() with self.assertRaises(configparser.NoSectionError) as cm: config.remove_option('Section1', 'an_int') - self.assertIs(cm.exception.__suppress_context__, True) + self.assertIsNone(cm.exception.__context__) class ConvertersTestCase(BasicTestCase, unittest.TestCase): diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 024f9126472..958b4dc084c 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -920,10 +920,10 @@ class TestBaseExitStack: exc = err_ctx.exception self.assertIsInstance(exc, UniqueException) - self.assertIsInstance(exc.__context__, UniqueRuntimeError) - self.assertIsNone(exc.__context__.__context__) - self.assertIsNone(exc.__context__.__cause__) - self.assertIs(exc.__cause__, exc.__context__) + self.assertIsInstance(exc.__cause__, UniqueRuntimeError) + self.assertIsNone(exc.__cause__.__context__) + self.assertIsNone(exc.__cause__.__cause__) + self.assertIsNone(exc.__context__) class TestExitStack(TestBaseExitStack, unittest.TestCase): diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 3a59a6102f4..03d80e3d94f 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -52,7 +52,9 @@ class BaseTestCase(unittest.TestCase): yield exc # Ensure we produce clean tracebacks on failure if exc.exception.__context__ is not None: - self.assertTrue(exc.exception.__suppress_context__) + self.assertFalse(exc.exception.__suppress_context__) + self.assertIsNone(exc.exception.__context__) + self.assertIsNone(exc.exception.__cause__) def assertAddressError(self, details, *args): """Ensure a clean AddressValueError""" diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 9e3a1695dfb..75df10e4df3 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -995,12 +995,19 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol): with self.assertRaises(KeyError) as cm: os.environ[missing] self.assertIs(cm.exception.args[0], missing) - self.assertTrue(cm.exception.__suppress_context__) + self.assertFalse(cm.exception.__suppress_context__) + self.assertIsNone(cm.exception.__context__) + self.assertIsNone(cm.exception.__cause__) - with self.assertRaises(KeyError) as cm: - del os.environ[missing] + try: + 1/0 + except: + with self.assertRaises(KeyError) as cm: + del os.environ[missing] self.assertIs(cm.exception.args[0], missing) - self.assertTrue(cm.exception.__suppress_context__) + self.assertFalse(cm.exception.__suppress_context__) + self.assertIsInstance(cm.exception.__context__, ZeroDivisionError) + self.assertIsNone(cm.exception.__cause__) def _test_environ_iteration(self, collection): iterator = iter(collection) diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py index 57da0e15a75..c1ff43eb0a4 100644 --- a/Lib/test/test_raise.py +++ b/Lib/test/test_raise.py @@ -77,14 +77,24 @@ class TestRaise(unittest.TestCase): nested_reraise() self.assertRaises(TypeError, reraise) - def test_raise_from_None(self): + def test_raise_from_None1(self): + try: + raise TypeError("foo") + except: + try: + raise ValueError() from None + except ValueError as e: + self.assertIsInstance(e.__context__, TypeError) + self.assertIsNone(e.__cause__) + + def test_raise_from_None2(self): try: try: raise TypeError("foo") except: raise ValueError() from None except ValueError as e: - self.assertIsInstance(e.__context__, TypeError) + self.assertIsNone(e.__context__) self.assertIsNone(e.__cause__) def test_with_reraise1(self): @@ -150,7 +160,27 @@ class TestRaise(unittest.TestCase): class TestCause(unittest.TestCase): - def testCauseSyntax(self): + def testCauseSyntax1(self): + try: + try: + raise TypeError + except Exception: + try: + raise ValueError from None + except ValueError as exc: + self.assertIsNone(exc.__cause__) + self.assertIsInstance(exc.__context__, TypeError) + self.assertTrue(exc.__suppress_context__) + exc.__suppress_context__ = False + raise exc + except ValueError as exc: + e = exc + + self.assertIsNone(e.__cause__) + self.assertFalse(e.__suppress_context__) + self.assertIsInstance(e.__context__, TypeError) + + def testCauseSyntax2(self): try: try: try: @@ -159,16 +189,106 @@ class TestCause(unittest.TestCase): raise ValueError from None except ValueError as exc: self.assertIsNone(exc.__cause__) - self.assertTrue(exc.__suppress_context__) - exc.__suppress_context__ = False + self.assertIsNone(exc.__context__) + self.assertFalse(exc.__suppress_context__) raise exc except ValueError as exc: e = exc + self.assertIsNone(e.__cause__) + self.assertFalse(e.__suppress_context__) + self.assertIsNone(e.__context__) + + def testCauseSyntax3(self): + try: + try: + 1/0 + except: + try: + raise TypeError + except Exception: + try: + raise ValueError from None + except ValueError as exc: + self.assertIsNone(exc.__cause__) + self.assertIsInstance(exc.__context__, TypeError) + self.assertTrue(exc.__suppress_context__) + exc.__suppress_context__ = False + raise exc + except ValueError as exc: + e = exc + self.assertIsNone(e.__cause__) self.assertFalse(e.__suppress_context__) self.assertIsInstance(e.__context__, TypeError) + def testCauseSyntax4(self): + try: + try: + try: + 1/0 + except: + try: + raise TypeError + except Exception: + raise ValueError from None + except ValueError as exc: + self.assertIsNone(exc.__cause__) + self.assertIsInstance(exc.__context__, ZeroDivisionError) + self.assertFalse(exc.__suppress_context__) + raise exc + except ValueError as exc: + e = exc + + self.assertIsNone(e.__cause__) + self.assertFalse(e.__suppress_context__) + self.assertIsInstance(e.__context__, ZeroDivisionError) + + def testCauseSyntax5(self): + try: + try: + 1/0 + except: + try: + raise TypeError + except Exception: + try: + raise ValueError from None + except ValueError as exc: + self.assertIsNone(exc.__cause__) + self.assertIsInstance(exc.__context__, TypeError) + self.assertTrue(exc.__suppress_context__) + raise exc + except ValueError as exc: + e = exc + + self.assertIsNone(e.__cause__) + self.assertFalse(e.__suppress_context__) + self.assertIsInstance(e.__context__, ZeroDivisionError) + + def testCauseSyntax6(self): + try: + try: + 1/0 + except: + try: + try: + raise TypeError + except Exception: + raise ValueError from None + except ValueError as exc: + self.assertIsNone(exc.__cause__) + self.assertIsInstance(exc.__context__, ZeroDivisionError) + self.assertFalse(exc.__suppress_context__) + exc.__suppress_context__ = True + raise exc + except ValueError as exc: + e = exc + + self.assertIsNone(e.__cause__) + self.assertFalse(e.__suppress_context__) + self.assertIsNone(e.__context__) + def test_invalid_cause(self): try: raise IndexError from 5 diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 55a0f426731..5966dd978de 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -243,13 +243,23 @@ class StrptimeTests(unittest.TestCase): def test_strptime_exception_context(self): # check that this doesn't chain exceptions needlessly (see #17572) - with self.assertRaises(ValueError) as e: - _strptime._strptime_time('', '%D') - self.assertIs(e.exception.__suppress_context__, True) + try: + 1/0 + except: + with self.assertRaises(ValueError) as e: + _strptime._strptime_time('', '%D') + self.assertFalse(e.exception.__suppress_context__) + self.assertIsInstance(e.exception.__context__, ZeroDivisionError) + self.assertIsNone(e.exception.__cause__) # additional check for IndexError branch (issue #19545) - with self.assertRaises(ValueError) as e: - _strptime._strptime_time('19', '%Y %') - self.assertIs(e.exception.__suppress_context__, True) + try: + 1/0 + except: + with self.assertRaises(ValueError) as e: + _strptime._strptime_time('19', '%Y %') + self.assertFalse(e.exception.__suppress_context__) + self.assertIsInstance(e.exception.__context__, ZeroDivisionError) + self.assertIsNone(e.exception.__cause__) def test_unconverteddata(self): # Check ValueError is raised when there is unconverted data diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 80e43fafad8..18a84a124e9 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -276,11 +276,15 @@ class TimeTestCase(unittest.TestCase): # check that this doesn't chain exceptions needlessly (see #17572) with self.assertRaises(ValueError) as e: time.strptime('', '%D') - self.assertIs(e.exception.__suppress_context__, True) + self.assertFalse(e.exception.__suppress_context__) + self.assertIsNone(e.exception.__context__) + self.assertIsNone(e.exception.__cause__) # additional check for IndexError branch (issue #19545) with self.assertRaises(ValueError) as e: time.strptime('19', '%Y %') - self.assertIs(e.exception.__suppress_context__, True) + self.assertFalse(e.exception.__suppress_context__) + self.assertIsNone(e.exception.__context__) + self.assertIsNone(e.exception.__cause__) def test_asctime(self): time.asctime(time.gmtime(self.t)) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 60e0b582756..aade2a9050f 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -1042,13 +1042,11 @@ class TestTracebackException(unittest.TestCase): self.assertEqual(exc_info[0], exc.exc_type) self.assertEqual(str(exc_info[1]), str(exc)) - def test_cause(self): + def test_cause1(self): try: try: 1/0 finally: - exc_info_context = sys.exc_info() - exc_context = traceback.TracebackException(*exc_info_context) cause = Exception("cause") raise Exception("uh oh") from cause except Exception: @@ -1057,12 +1055,37 @@ class TestTracebackException(unittest.TestCase): expected_stack = traceback.StackSummary.extract( traceback.walk_tb(exc_info[2])) exc_cause = traceback.TracebackException(Exception, cause, None) - self.assertEqual(exc_cause, exc.__cause__) - self.assertEqual(exc_context, exc.__context__) - self.assertEqual(True, exc.__suppress_context__) - self.assertEqual(expected_stack, exc.stack) - self.assertEqual(exc_info[0], exc.exc_type) - self.assertEqual(str(exc_info[1]), str(exc)) + self.assertEqual(exc.__cause__, exc_cause) + self.assertIsNone(exc.__context__) + self.assertFalse(exc.__suppress_context__) + self.assertEqual(exc.stack, expected_stack) + self.assertEqual(exc.exc_type, exc_info[0]) + self.assertEqual(str(exc), str(exc_info[1])) + + def test_cause2(self): + try: + try: + raise TypeError + except: + exc_info_context = sys.exc_info() + exc_context = traceback.TracebackException(*exc_info_context) + try: + 1/0 + finally: + cause = Exception("cause") + raise Exception("uh oh") from cause + except Exception: + exc_info = sys.exc_info() + exc = traceback.TracebackException(*exc_info) + expected_stack = traceback.StackSummary.extract( + traceback.walk_tb(exc_info[2])) + exc_cause = traceback.TracebackException(Exception, cause, None) + self.assertEqual(exc.__cause__, exc_cause) + self.assertEqual(exc.__context__, exc_context) + self.assertFalse(exc.__suppress_context__) + self.assertEqual(exc.stack, expected_stack) + self.assertEqual(exc.exc_type, exc_info[0]) + self.assertEqual(str(exc), str(exc_info[1])) def test_context(self): try: diff --git a/Python/ceval.c b/Python/ceval.c index 3f65820c25d..37eb16119b8 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3608,6 +3608,23 @@ exception_unwind: if (b->b_type == EXCEPT_HANDLER) { UNWIND_EXCEPT_HANDLER(b); + if (tstate->curexc_value != NULL) { + assert(PyExceptionInstance_Check(tstate->curexc_value)); + PyBaseExceptionObject *curexc_value = (PyBaseExceptionObject *)tstate->curexc_value; + if (curexc_value->suppress_context) { + PyObject *exc_value = _PyErr_GetTopmostException(tstate)->exc_value; + if (exc_value == Py_None) { + exc_value = NULL; + } + if ((PyObject *)curexc_value != exc_value && + curexc_value->context != exc_value) + { + curexc_value->suppress_context = 0; + Py_XINCREF(exc_value); + PyException_SetContext((PyObject *)curexc_value, exc_value); + } + } + } continue; } UNWIND_BLOCK(b);