Skip to content

Commit 9561648

Browse files
authored
gh-110235: Raise TypeError for duplicate/unknown fields in PyStructSequence constructor (GH-110258)
1 parent bf4bc36 commit 9561648

File tree

3 files changed

+84
-7
lines changed

3 files changed

+84
-7
lines changed

Lib/test/test_structseq.py

+60
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import copy
22
import os
33
import pickle
4+
import re
45
import time
56
import unittest
67

@@ -91,10 +92,69 @@ def test_constructor(self):
9192
self.assertRaises(TypeError, t, "123")
9293
self.assertRaises(TypeError, t, "123", dict={})
9394
self.assertRaises(TypeError, t, "123456789", dict=None)
95+
self.assertRaises(TypeError, t, seq="123456789", dict={})
96+
97+
self.assertEqual(t("123456789"), tuple("123456789"))
98+
self.assertEqual(t("123456789", {}), tuple("123456789"))
99+
self.assertEqual(t("123456789", dict={}), tuple("123456789"))
100+
self.assertEqual(t(sequence="123456789", dict={}), tuple("123456789"))
101+
102+
self.assertEqual(t("1234567890"), tuple("123456789"))
103+
self.assertEqual(t("1234567890").tm_zone, "0")
104+
self.assertEqual(t("123456789", {"tm_zone": "some zone"}), tuple("123456789"))
105+
self.assertEqual(t("123456789", {"tm_zone": "some zone"}).tm_zone, "some zone")
94106

95107
s = "123456789"
96108
self.assertEqual("".join(t(s)), s)
97109

110+
def test_constructor_with_duplicate_fields(self):
111+
t = time.struct_time
112+
113+
error_message = re.escape("got duplicate or unexpected field name(s)")
114+
with self.assertRaisesRegex(TypeError, error_message):
115+
t("1234567890", dict={"tm_zone": "some zone"})
116+
with self.assertRaisesRegex(TypeError, error_message):
117+
t("1234567890", dict={"tm_zone": "some zone", "tm_mon": 1})
118+
with self.assertRaisesRegex(TypeError, error_message):
119+
t("1234567890", dict={"error": 0, "tm_zone": "some zone"})
120+
with self.assertRaisesRegex(TypeError, error_message):
121+
t("1234567890", dict={"error": 0, "tm_zone": "some zone", "tm_mon": 1})
122+
123+
def test_constructor_with_duplicate_unnamed_fields(self):
124+
assert os.stat_result.n_unnamed_fields > 0
125+
n_visible_fields = os.stat_result.n_sequence_fields
126+
127+
r = os.stat_result(range(n_visible_fields), {'st_atime': -1.0})
128+
self.assertEqual(r.st_atime, -1.0)
129+
self.assertEqual(r, tuple(range(n_visible_fields)))
130+
131+
r = os.stat_result((*range(n_visible_fields), -1.0))
132+
self.assertEqual(r.st_atime, -1.0)
133+
self.assertEqual(r, tuple(range(n_visible_fields)))
134+
135+
with self.assertRaisesRegex(TypeError,
136+
re.escape("got duplicate or unexpected field name(s)")):
137+
os.stat_result((*range(n_visible_fields), -1.0), {'st_atime': -1.0})
138+
139+
def test_constructor_with_unknown_fields(self):
140+
t = time.struct_time
141+
142+
error_message = re.escape("got duplicate or unexpected field name(s)")
143+
with self.assertRaisesRegex(TypeError, error_message):
144+
t("123456789", dict={"tm_year": 0})
145+
with self.assertRaisesRegex(TypeError, error_message):
146+
t("123456789", dict={"tm_year": 0, "tm_mon": 1})
147+
with self.assertRaisesRegex(TypeError, error_message):
148+
t("123456789", dict={"tm_zone": "some zone", "tm_mon": 1})
149+
with self.assertRaisesRegex(TypeError, error_message):
150+
t("123456789", dict={"tm_zone": "some zone", "error": 0})
151+
with self.assertRaisesRegex(TypeError, error_message):
152+
t("123456789", dict={"error": 0, "tm_zone": "some zone", "tm_mon": 1})
153+
with self.assertRaisesRegex(TypeError, error_message):
154+
t("123456789", dict={"error": 0})
155+
with self.assertRaisesRegex(TypeError, error_message):
156+
t("123456789", dict={"tm_zone": "some zone", "error": 0})
157+
98158
def test_eviltuple(self):
99159
class Exc(Exception):
100160
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Raise :exc:`TypeError` for duplicate/unknown fields in ``PyStructSequence`` constructor.
2+
Patched by Xuehai Pan.

Objects/structseq.c

+22-7
Original file line numberDiff line numberDiff line change
@@ -216,19 +216,34 @@ structseq_new_impl(PyTypeObject *type, PyObject *arg, PyObject *dict)
216216
res->ob_item[i] = Py_NewRef(v);
217217
}
218218
Py_DECREF(arg);
219-
for (; i < max_len; ++i) {
220-
PyObject *ob = NULL;
221-
if (dict != NULL) {
222-
const char *name = type->tp_members[i-n_unnamed_fields].name;
219+
if (dict != NULL && PyDict_GET_SIZE(dict) > 0) {
220+
Py_ssize_t n_found_keys = 0;
221+
for (i = len; i < max_len; ++i) {
222+
PyObject *ob = NULL;
223+
const char *name = type->tp_members[i - n_unnamed_fields].name;
223224
if (PyDict_GetItemStringRef(dict, name, &ob) < 0) {
224225
Py_DECREF(res);
225226
return NULL;
226227
}
228+
if (ob == NULL) {
229+
ob = Py_NewRef(Py_None);
230+
}
231+
else {
232+
++n_found_keys;
233+
}
234+
res->ob_item[i] = ob;
235+
}
236+
if (PyDict_GET_SIZE(dict) > n_found_keys) {
237+
PyErr_Format(PyExc_TypeError,
238+
"%.500s() got duplicate or unexpected field name(s)",
239+
type->tp_name);
240+
Py_DECREF(res);
241+
return NULL;
227242
}
228-
if (ob == NULL) {
229-
ob = Py_NewRef(Py_None);
243+
} else {
244+
for (i = len; i < max_len; ++i) {
245+
res->ob_item[i] = Py_NewRef(Py_None);
230246
}
231-
res->ob_item[i] = ob;
232247
}
233248

234249
_PyObject_GC_TRACK(res);

0 commit comments

Comments
 (0)