bpo-39019: Suppress only handled exception by "raise ... from ...".

This commit is contained in:
Serhiy Storchaka 2020-02-23 23:51:02 +02:00
parent a025d4ca99
commit d5aacefb01
10 changed files with 240 additions and 49 deletions

View File

@ -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))

View File

@ -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):

View File

@ -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):

View File

@ -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"""

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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:

View File

@ -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);