| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -923,6 +923,148 @@ def __del__(self): | |||
| 923 | 923 | pass | |
| 924 | 924 | self.assertEqual(e, (None, None, None)) | |
| 925 | 925 | ||
| 926 | + def test_raise_does_not_create_context_chain_cycle(self): | ||
| 927 | + class A(Exception): | ||
| 928 | + pass | ||
| 929 | + class B(Exception): | ||
| 930 | + pass | ||
| 931 | + class C(Exception): | ||
| 932 | + pass | ||
| 933 | + | ||
| 934 | + # Create a context chain: | ||
| 935 | + # C -> B -> A | ||
| 936 | + # Then raise A in context of C. | ||
| 937 | + try: | ||
| 938 | + try: | ||
| 939 | + raise A | ||
| 940 | + except A as a_: | ||
| 941 | + a = a_ | ||
| 942 | + try: | ||
| 943 | + raise B | ||
| 944 | + except B as b_: | ||
| 945 | + b = b_ | ||
| 946 | + try: | ||
| 947 | + raise C | ||
| 948 | + except C as c_: | ||
| 949 | + c = c_ | ||
| 950 | + self.assertIsInstance(a, A) | ||
| 951 | + self.assertIsInstance(b, B) | ||
| 952 | + self.assertIsInstance(c, C) | ||
| 953 | + self.assertIsNone(a.__context__) | ||
| 954 | + self.assertIs(b.__context__, a) | ||
| 955 | + self.assertIs(c.__context__, b) | ||
| 956 | + raise a | ||
| 957 | + except A as e: | ||
| 958 | + exc = e | ||
| 959 | + | ||
| 960 | + # Expect A -> C -> B, without cycle | ||
| 961 | + self.assertIs(exc, a) | ||
| 962 | + self.assertIs(a.__context__, c) | ||
| 963 | + self.assertIs(c.__context__, b) | ||
| 964 | + self.assertIsNone(b.__context__) | ||
| 965 | + | ||
| 966 | + def test_no_hang_on_context_chain_cycle1(self): | ||
| 967 | + # See issue 25782. Cycle in context chain. | ||
| 968 | + | ||
| 969 | + def cycle(): | ||
| 970 | + try: | ||
| 971 | + raise ValueError(1) | ||
| 972 | + except ValueError as ex: | ||
| 973 | + ex.__context__ = ex | ||
| 974 | + raise TypeError(2) | ||
| 975 | + | ||
| 976 | + try: | ||
| 977 | + cycle() | ||
| 978 | + except Exception as e: | ||
| 979 | + exc = e | ||
| 980 | + | ||
| 981 | + self.assertIsInstance(exc, TypeError) | ||
| 982 | + self.assertIsInstance(exc.__context__, ValueError) | ||
| 983 | + self.assertIs(exc.__context__.__context__, exc.__context__) | ||
| 984 | + | ||
| 985 | + def test_no_hang_on_context_chain_cycle2(self): | ||
| 986 | + # See issue 25782. Cycle at head of context chain. | ||
| 987 | + | ||
| 988 | + class A(Exception): | ||
| 989 | + pass | ||
| 990 | + class B(Exception): | ||
| 991 | + pass | ||
| 992 | + class C(Exception): | ||
| 993 | + pass | ||
| 994 | + | ||
| 995 | + # Context cycle: | ||
| 996 | + # +-----------+ | ||
| 997 | + # V | | ||
| 998 | + # C --> B --> A | ||
| 999 | + with self.assertRaises(C) as cm: | ||
| 1000 | + try: | ||
| 1001 | + raise A() | ||
| 1002 | + except A as _a: | ||
| 1003 | + a = _a | ||
| 1004 | + try: | ||
| 1005 | + raise B() | ||
| 1006 | + except B as _b: | ||
| 1007 | + b = _b | ||
| 1008 | + try: | ||
| 1009 | + raise C() | ||
| 1010 | + except C as _c: | ||
| 1011 | + c = _c | ||
| 1012 | + a.__context__ = c | ||
| 1013 | + raise c | ||
| 1014 | + | ||
| 1015 | + self.assertIs(cm.exception, c) | ||
| 1016 | + # Verify the expected context chain cycle | ||
| 1017 | + self.assertIs(c.__context__, b) | ||
| 1018 | + self.assertIs(b.__context__, a) | ||
| 1019 | + self.assertIs(a.__context__, c) | ||
| 1020 | + | ||
| 1021 | + def test_no_hang_on_context_chain_cycle3(self): | ||
| 1022 | + # See issue 25782. Longer context chain with cycle. | ||
| 1023 | + | ||
| 1024 | + class A(Exception): | ||
| 1025 | + pass | ||
| 1026 | + class B(Exception): | ||
| 1027 | + pass | ||
| 1028 | + class C(Exception): | ||
| 1029 | + pass | ||
| 1030 | + class D(Exception): | ||
| 1031 | + pass | ||
| 1032 | + class E(Exception): | ||
| 1033 | + pass | ||
| 1034 | + | ||
| 1035 | + # Context cycle: | ||
| 1036 | + # +-----------+ | ||
| 1037 | + # V | | ||
| 1038 | + # E --> D --> C --> B --> A | ||
| 1039 | + with self.assertRaises(E) as cm: | ||
| 1040 | + try: | ||
| 1041 | + raise A() | ||
| 1042 | + except A as _a: | ||
| 1043 | + a = _a | ||
| 1044 | + try: | ||
| 1045 | + raise B() | ||
| 1046 | + except B as _b: | ||
| 1047 | + b = _b | ||
| 1048 | + try: | ||
| 1049 | + raise C() | ||
| 1050 | + except C as _c: | ||
| 1051 | + c = _c | ||
| 1052 | + a.__context__ = c | ||
| 1053 | + try: | ||
| 1054 | + raise D() | ||
| 1055 | + except D as _d: | ||
| 1056 | + d = _d | ||
| 1057 | + e = E() | ||
| 1058 | + raise e | ||
| 1059 | + | ||
| 1060 | + self.assertIs(cm.exception, e) | ||
| 1061 | + # Verify the expected context chain cycle | ||
| 1062 | + self.assertIs(e.__context__, d) | ||
| 1063 | + self.assertIs(d.__context__, c) | ||
| 1064 | + self.assertIs(c.__context__, b) | ||
| 1065 | + self.assertIs(b.__context__, a) | ||
| 1066 | + self.assertIs(a.__context__, c) | ||
| 1067 | + | ||
| 926 | 1068 | def test_unicode_change_attributes(self): | |
| 927 | 1069 | # See issue 7309. This was a crasher. | |
| 928 | 1070 | ||
| 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