| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -953,6 +953,148 @@ def __del__(self): | |||
| 953 | 953 | pass | |
| 954 | 954 | self.assertEqual(e, (None, None, None)) | |
| 955 | 955 | ||
| 956 | + def test_raise_does_not_create_context_chain_cycle(self): | ||
| 957 | + class A(Exception): | ||
| 958 | + pass | ||
| 959 | + class B(Exception): | ||
| 960 | + pass | ||
| 961 | + class C(Exception): | ||
| 962 | + pass | ||
| 963 | + | ||
| 964 | + # Create a context chain: | ||
| 965 | + # C -> B -> A | ||
| 966 | + # Then raise A in context of C. | ||
| 967 | + try: | ||
| 968 | + try: | ||
| 969 | + raise A | ||
| 970 | + except A as a_: | ||
| 971 | + a = a_ | ||
| 972 | + try: | ||
| 973 | + raise B | ||
| 974 | + except B as b_: | ||
| 975 | + b = b_ | ||
| 976 | + try: | ||
| 977 | + raise C | ||
| 978 | + except C as c_: | ||
| 979 | + c = c_ | ||
| 980 | + self.assertIsInstance(a, A) | ||
| 981 | + self.assertIsInstance(b, B) | ||
| 982 | + self.assertIsInstance(c, C) | ||
| 983 | + self.assertIsNone(a.__context__) | ||
| 984 | + self.assertIs(b.__context__, a) | ||
| 985 | + self.assertIs(c.__context__, b) | ||
| 986 | + raise a | ||
| 987 | + except A as e: | ||
| 988 | + exc = e | ||
| 989 | + | ||
| 990 | + # Expect A -> C -> B, without cycle | ||
| 991 | + self.assertIs(exc, a) | ||
| 992 | + self.assertIs(a.__context__, c) | ||
| 993 | + self.assertIs(c.__context__, b) | ||
| 994 | + self.assertIsNone(b.__context__) | ||
| 995 | + | ||
| 996 | + def test_no_hang_on_context_chain_cycle1(self): | ||
| 997 | + # See issue 25782. Cycle in context chain. | ||
| 998 | + | ||
| 999 | + def cycle(): | ||
| 1000 | + try: | ||
| 1001 | + raise ValueError(1) | ||
| 1002 | + except ValueError as ex: | ||
| 1003 | + ex.__context__ = ex | ||
| 1004 | + raise TypeError(2) | ||
| 1005 | + | ||
| 1006 | + try: | ||
| 1007 | + cycle() | ||
| 1008 | + except Exception as e: | ||
| 1009 | + exc = e | ||
| 1010 | + | ||
| 1011 | + self.assertIsInstance(exc, TypeError) | ||
| 1012 | + self.assertIsInstance(exc.__context__, ValueError) | ||
| 1013 | + self.assertIs(exc.__context__.__context__, exc.__context__) | ||
| 1014 | + | ||
| 1015 | + def test_no_hang_on_context_chain_cycle2(self): | ||
| 1016 | + # See issue 25782. Cycle at head of context chain. | ||
| 1017 | + | ||
| 1018 | + class A(Exception): | ||
| 1019 | + pass | ||
| 1020 | + class B(Exception): | ||
| 1021 | + pass | ||
| 1022 | + class C(Exception): | ||
| 1023 | + pass | ||
| 1024 | + | ||
| 1025 | + # Context cycle: | ||
| 1026 | + # +-----------+ | ||
| 1027 | + # V | | ||
| 1028 | + # C --> B --> A | ||
| 1029 | + with self.assertRaises(C) as cm: | ||
| 1030 | + try: | ||
| 1031 | + raise A() | ||
| 1032 | + except A as _a: | ||
| 1033 | + a = _a | ||
| 1034 | + try: | ||
| 1035 | + raise B() | ||
| 1036 | + except B as _b: | ||
| 1037 | + b = _b | ||
| 1038 | + try: | ||
| 1039 | + raise C() | ||
| 1040 | + except C as _c: | ||
| 1041 | + c = _c | ||
| 1042 | + a.__context__ = c | ||
| 1043 | + raise c | ||
| 1044 | + | ||
| 1045 | + self.assertIs(cm.exception, c) | ||
| 1046 | + # Verify the expected context chain cycle | ||
| 1047 | + self.assertIs(c.__context__, b) | ||
| 1048 | + self.assertIs(b.__context__, a) | ||
| 1049 | + self.assertIs(a.__context__, c) | ||
| 1050 | + | ||
| 1051 | + def test_no_hang_on_context_chain_cycle3(self): | ||
| 1052 | + # See issue 25782. Longer context chain with cycle. | ||
| 1053 | + | ||
| 1054 | + class A(Exception): | ||
| 1055 | + pass | ||
| 1056 | + class B(Exception): | ||
| 1057 | + pass | ||
| 1058 | + class C(Exception): | ||
| 1059 | + pass | ||
| 1060 | + class D(Exception): | ||
| 1061 | + pass | ||
| 1062 | + class E(Exception): | ||
| 1063 | + pass | ||
| 1064 | + | ||
| 1065 | + # Context cycle: | ||
| 1066 | + # +-----------+ | ||
| 1067 | + # V | | ||
| 1068 | + # E --> D --> C --> B --> A | ||
| 1069 | + with self.assertRaises(E) as cm: | ||
| 1070 | + try: | ||
| 1071 | + raise A() | ||
| 1072 | + except A as _a: | ||
| 1073 | + a = _a | ||
| 1074 | + try: | ||
| 1075 | + raise B() | ||
| 1076 | + except B as _b: | ||
| 1077 | + b = _b | ||
| 1078 | + try: | ||
| 1079 | + raise C() | ||
| 1080 | + except C as _c: | ||
| 1081 | + c = _c | ||
| 1082 | + a.__context__ = c | ||
| 1083 | + try: | ||
| 1084 | + raise D() | ||
| 1085 | + except D as _d: | ||
| 1086 | + d = _d | ||
| 1087 | + e = E() | ||
| 1088 | + raise e | ||
| 1089 | + | ||
| 1090 | + self.assertIs(cm.exception, e) | ||
| 1091 | + # Verify the expected context chain cycle | ||
| 1092 | + self.assertIs(e.__context__, d) | ||
| 1093 | + self.assertIs(d.__context__, c) | ||
| 1094 | + self.assertIs(c.__context__, b) | ||
| 1095 | + self.assertIs(b.__context__, a) | ||
| 1096 | + self.assertIs(a.__context__, c) | ||
| 1097 | + | ||
| 956 | 1098 | def test_unicode_change_attributes(self): | |
| 957 | 1099 | # See issue 7309. This was a crasher. | |
| 958 | 1100 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + Fix bug where ``PyErr_SetObject`` hangs when the current exception has a cycle in its context chain. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -148,19 +148,33 @@ _PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value) | |||
| 148 | 148 | value = fixed_value; | |
| 149 | 149 | } | |
| 150 | 150 | ||
| 151 | - /* Avoid reference cycles through the context chain. | ||
| 151 | + /* Avoid creating new reference cycles through the | ||
| 152 | + context chain, while taking care not to hang on | ||
| 153 | + pre-existing ones. | ||
| 152 | 154 | This is O(chain length) but context chains are | |
| 153 | 155 | usually very short. Sensitive readers may try | |
| 154 | 156 | to inline the call to PyException_GetContext. */ | |
| 155 | 157 | if (exc_value != value) { | |
| 156 | 158 | PyObject *o = exc_value, *context; | |
| 159 | + PyObject *slow_o = o; /* Floyd's cycle detection algo */ | ||
| 160 | + int slow_update_toggle = 0; | ||
| 157 | 161 | while ((context = PyException_GetContext(o))) { | |
| 158 | 162 | Py_DECREF(context); | |
| 159 | 163 | if (context == value) { | |
| 160 | 164 | PyException_SetContext(o, NULL); | |
| 161 | 165 | break; | |
| 162 | 166 | } | |
| 163 | 167 | o = context; | |
| 168 | + if (o == slow_o) { | ||
| 169 | + /* pre-existing cycle - all exceptions on the | ||
| 170 | + path were visited and checked. */ | ||
| 171 | + break; | ||
| 172 | + } | ||
| 173 | + if (slow_update_toggle) { | ||
| 174 | + slow_o = PyException_GetContext(slow_o); | ||
| 175 | + Py_DECREF(slow_o); | ||
| 176 | + } | ||
| 177 | + slow_update_toggle = !slow_update_toggle; | ||
| 164 | 178 | } | |
| 165 | 179 | PyException_SetContext(value, exc_value); | |
| 166 | 180 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments